package com.xbongbong.pro.service.toolbox.statistic.help;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.i18n.util.I18nMessageUtil;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.domain.entity.PaasStageEntity;
import com.xbongbong.paas.domain.entity.ext.ConditionsEntityExt;
import com.xbongbong.paas.elasticsearch.pojo.AggMorePojo;
import com.xbongbong.paas.elasticsearch.util.EsHelper;
import com.xbongbong.paas.elasticsearch.util.EsUtil;
import com.xbongbong.paas.elasticsearch.util.XbbElasticsearchRestTemplate;
import com.xbongbong.paas.enums.ConditionEnum;
import com.xbongbong.paas.enums.IndexTypeEnum;
import com.xbongbong.paas.enums.StarEnum;
import com.xbongbong.paas.field.ItemPoJo;
import com.xbongbong.paas.model.PaasFormModel;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.Arith;
import com.xbongbong.paas.toolbox.util.BeanUtil;
import com.xbongbong.paas.toolbox.util.CollectionsUtil;
import com.xbongbong.paas.toolbox.util.DateUtil;
import com.xbongbong.paas.toolbox.util.MD5Util;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.paas.toolbox.wrap.UserVO;
import com.xbongbong.pro.constant.CommonConstant;
import com.xbongbong.pro.constant.ElasticConstant;
import com.xbongbong.pro.constant.I18nStringConstant;
import com.xbongbong.pro.constant.MapConstant;
import com.xbongbong.pro.constant.SymbolConstant;
import com.xbongbong.pro.domain.entity.ChartEntity;
import com.xbongbong.pro.domain.entity.HomePageManageEntity;
import com.xbongbong.pro.domain.entity.IndexSetEntity;
import com.xbongbong.pro.enums.BoardTypeEnum;
import com.xbongbong.pro.enums.SaasMarkEnum;
import com.xbongbong.pro.enums.TimeTypeEnum;
import com.xbongbong.pro.enums.errorcodes.StatisticsServicesErrorCodeEnum;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.pro.index.pojo.IndexSetDataPojo;
import com.xbongbong.pro.service.toolbox.statistic.util.ChartResultUtil;
import com.xbongbong.pro.statistic.constant.ChartConstant;
import com.xbongbong.pro.statistic.enums.ChartTypeEnum;
import com.xbongbong.pro.statistic.enums.NumFormatEnum;
import com.xbongbong.pro.statistic.enums.ReturnTypeEnum;
import com.xbongbong.pro.statistic.enums.StatisticsTypeEnum;
import com.xbongbong.pro.statistic.enums.SystemCodeEnum;
import com.xbongbong.pro.statistic.enums.ValueTypeEnum;
import com.xbongbong.pro.statistic.pojo.FiscalYearTimePojo;
import com.xbongbong.pro.statistic.pojo.TimePojo;
import com.xbongbong.pro.statistic.pojo.vo.ContractInfoVO;
import com.xbongbong.pro.statistic.pojo.vo.ContractPerformanceInfoVO;
import com.xbongbong.pro.statistic.pojo.vo.CustomerInfoVO;
import com.xbongbong.pro.statistic.result.pojo.AggsBucketsPojo;
import com.xbongbong.pro.statistic.result.pojo.ChartResultBasePojo;
import com.xbongbong.pro.statistic.result.pojo.ChartResultPkPojo;
import com.xbongbong.pro.statistic.result.pojo.ChartResultPojo;
import com.xbongbong.pro.statistic.result.pojo.ChartResultRedisPojo;
import com.xbongbong.pro.statistic.result.pojo.CustomerOkFirstSecondPojo;
import com.xbongbong.pro.statistic.result.pojo.ElasticAggsPojo;
import com.xbongbong.pro.statistic.result.pojo.FloorAggsKeyPojo;
import com.xbongbong.pro.statistic.result.pojo.StageFunnelInfoPojo;
import com.xbongbong.pro.statistic.result.pojo.TimeAmountPojo;
import com.xbongbong.pro.statistic.result.pojo.TitleRightPojo;
import com.xbongbong.pro.statistic.result.pojo.TopResultPojo;
import com.xbongbong.pro.statistic.result.pojo.TotalAndAggPojo;
import com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultInfoDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultPieInfoDTO;
import com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultChartVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultCountListVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultDataChartVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultDataSeriesChartVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultDataTableVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultDoubleListVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultTableVO;
import com.xbongbong.pro.statistic.through.pojo.dto.ThroughDTO;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.ErrorMessageConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.domain.entity.ext.WorkOrderEvaluateRateStarEntityExt;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.saas.enums.business.ContractEnum;
import com.xbongbong.saas.enums.business.CustomerManagementEnum;
import com.xbongbong.saas.enums.business.OrderEnum;
import com.xbongbong.saas.enums.business.PaymentEnum;
import com.xbongbong.saas.enums.business.PaymentSheetEnum;
import com.xbongbong.saas.enums.business.ProductEnum;
import com.xbongbong.saas.enums.dictionary.OrderStatusEnum;
import com.xbongbong.saas.enums.subform.ContractProductSubFormEnum;
import com.xbongbong.saas.model.HomePageManageModel;
import com.xbongbong.saas.model.IndexSetModel;
import com.xbongbong.saas.model.IndexTypeModel;
import com.xbongbong.saas.service.toolbox.help.ScriptHelp;
import com.xbongbong.saas.service.toolbox.statistic.help.ChartHelp;
import com.xbongbong.saas.toolbox.help.BasicHelper;
import com.xbongbong.saas.toolbox.help.SortHelp;
import com.xbongbong.sys.enums.CompanyStructTypeEnum;
import org.apache.poi.ss.formula.functions.T;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.join.aggregations.ChildrenAggregationBuilder;
import org.elasticsearch.join.aggregations.JoinAggregationBuilders;
import org.elasticsearch.join.query.HasParentQueryBuilder;
import org.elasticsearch.join.query.JoinQueryBuilders;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.CardinalityAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
import org.elasticsearch.search.aggregations.pipeline.SumBucketPipelineAggregationBuilder;
import org.elasticsearch.search.aggregations.support.ValuesSourceAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;

import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;

/**
 * 计算系统图表统计结果帮助类
 * @author zcp
 * @version v1.0
 * @date 2019/2/18 21:33
 * @since v1.0
 */
@Component
public class SystemChartHelp {
    private static final Logger LOG = LoggerFactory.getLogger(SystemChartHelp.class);
    @Resource
    private EsHelper esHelper;
    @Resource
    private ChartDtoHelp chartDtoHelp;
    @Resource
    private FormHelp formHelp;
    @Resource
    private ThroughHelp throughHelp;
    @Resource
    private ChartQueryHelp chartQueryHelp;
    @Resource
    private PaasFormModel paasFormModel;
    @Resource
    private XbbElasticsearchRestTemplate xbbElasticsearchRestTemplate;
    @Resource
    private ChartHelp chartHelp;
    @Resource
    private ChartRuleQueryHelp chartRuleQueryHelp;
    @Resource
    private IndexSetModel indexSetModel;
    @Resource
    private HomePageManageModel homePageManageModel;
    @Resource
    private IndexTypeModel indexTypeModel;

    /**
     * 获取单个数值总数
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @return int 总数
     * @author zcp
     * @date 2019/2/19 15:49
     * @since v1.0
     * @version v1.0
     */
    public int getCount4Total(IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder) throws XbbException {
       return chartHelp.getCount4Total(indexTypeEnum, boolQueryBuilder);
    }

    /**
     * 求和int型字段
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param sumField         求和的字段
     * @return double
     * @author zcp
     * @date 2019/6/3 17:16
     * @since v1.0
     * @version v1.0
     */
    public TotalAndAggPojo getInt4Total(IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String sumField) throws XbbException {
        SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("aggs01").field(sumField);
        JSONObject jsonObject = esHelper.elasticAggs4NoBuckets2Object(indexTypeEnum, boolQueryBuilder, sumAggregationBuilder);
        JSONObject aggregations = jsonObject.getJSONObject("aggregations");
        int value = aggregations.getJSONObject("aggs01").getIntValue("value");
        int total = jsonObject.getIntValue("total");
        TotalAndAggPojo totalAndAggPojo = new TotalAndAggPojo();
        totalAndAggPojo.setTotal(total);
        totalAndAggPojo.setAgg(value);
        return totalAndAggPojo;
    }

    /**
     * 求和double型字段
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param sumField         求和的字段
     * @return double 总额
     * @author youli.chen
     * @date 2019/2/27 12:22
     * indexTypeEnum 核对完成 已更改为indexModel中方法 @date 2022/5/6 15:35
     * @since v1.0
     * @version v1.0
     */
    public double getDouble4Total(IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String sumField) throws XbbException {
        SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("aggs01").field(sumField);
        JSONObject jsonObject = esHelper.elasticAggs4NoBuckets2Object(indexTypeEnum, boolQueryBuilder, sumAggregationBuilder);
        JSONObject aggregations = jsonObject.getJSONObject("aggregations");
        Double value = aggregations.getJSONObject("aggs01").getDouble("value");
        //保留2位小数
        double total = Arith.div(value, 1, 2);
        return total;
    }

    /**
     * 废弃原因：pro无多币种
     * 获取总金额（带多币种）
     * 双层聚合：第一层币种，第二层金额求和
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param currencyField    要聚合的币种字段
     * @param sumField         求和的金额字段
     * @param currencyRateMap  币种id--币种汇率
     * @return double 总金额
     * @author zcp
     * @date 2019/2/27 12:22
     * @since v1.0
     * @version v1.0
     */
    @Deprecated
    public double getAmount4Total(IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String currencyField, String sumField, Map<Long, Double> currencyRateMap) throws XbbException {
        TermsAggregationBuilder termsAggregationBuilder = esHelper.getTermsAggregation4Two(currencyField, sumField, "aggs01", "aggs02");
        JSONObject retJson = esHelper.elasticAggs4Buckets2Object(indexTypeEnum, boolQueryBuilder, termsAggregationBuilder);

        JSONArray jsonArray = retJson.getJSONArray("buckets");
        double total = 0;
        for (Object object : jsonArray) {
            double amount = esHelper.getBucketsAmount(currencyRateMap, object, "aggs02");
            total += amount;
        }
        //保留2位小数
        total = Arith.div(total, 1, 2);
        return total;
    }

    /**
     * 废弃原因：pro无多币种
     * 获取回款的总金额：回款的币种基于合同，因此需要特殊处理
     * 双层聚合：第一层合同id分组，第二层金额求和
     * @param indexTypeEnum    索引，用于获取index和type
     * @param chartResultPojo  参数pojo
     * @param boolQueryBuilder 查询条件
     * @param sumField         时间求和字段
     * @param currencyRateMap  币种id--币种汇率
     * @return double 回款总金额
     * @author zcp
     * @date 2019/2/28 17:35
     * @since v1.0
     * @version v1.0
     */
    @Deprecated
    public double getPayment4Total(IndexTypeEnum indexTypeEnum, ChartResultPojo chartResultPojo, BoolQueryBuilder boolQueryBuilder, String sumField, Map<Long, Double> currencyRateMap) throws XbbException {
        String corpid = chartResultPojo.getCorpid();
        //按照合同id分组
        String contractIdAttr = PaymentSheetEnum.getEsAttr4Keyword(PaymentSheetEnum.CONTRACT);
        //-------------------通过回款关联的合同id，得到对应币种
        TermsAggregationBuilder termsAggregationBuilder = esHelper.getTermsAggregation4Two(contractIdAttr, sumField, "aggs01", "aggs02");
        JSONObject retJson = esHelper.elasticAggs4Buckets2Object(indexTypeEnum, boolQueryBuilder, termsAggregationBuilder);

        JSONArray jsonArray = retJson.getJSONArray("buckets");
        //合同id--属于该合同的回款单总金额
        Map<Long, Double> cidAmountMap = new HashMap<>(jsonArray.size());
        Set<Long> contractIdIn = new HashSet<>();
        for (Object object : jsonArray) {
            JSONObject jsonObject = (JSONObject) object;
            //桶内各个合同id
            Long contractId = jsonObject.getLong("key");
            //该合同对应的金额
            Double value = jsonObject.getJSONObject("aggs02").getDoubleValue("value");

            cidAmountMap.put(contractId, value);
            contractIdIn.add(contractId);
        }
        //-------------------处理币种
        return convertPaymentAmount(currencyRateMap, corpid, cidAmountMap, contractIdIn);
    }

    /**
     * 废弃原因：pro无多币种
     * 获取未收款的总金额：未收款（应收款）的币种基于合同，因此需要特殊处理
     * 双层聚合：第一层合同id分组，第二层金额求和
     * @param chartResultPojo  参数pojo
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param currencyRateMap  币种id--币种汇率
     * @return double 回款总金额
     * @throws XbbException
     * @author zcp
     * @date 2019/3/3 14:47
     * @since v1.0
     * @version v1.0
     */
    @Deprecated
    public double getPaymentUnAmount(ChartResultPojo chartResultPojo, IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, Map<Long, Double> currencyRateMap) throws XbbException {
        String corpid = chartResultPojo.getCorpid();
        //按照合同id分组
        String contractIdAttr = PaymentEnum.getEsAttr4Keyword(PaymentEnum.CONTRACT);
        String sumField = PaymentEnum.getEsAttr4Keyword(PaymentEnum.UN_AMOUNT);
        TermsAggregationBuilder termsAggregationBuilder = esHelper.getTermsAggregation4Two(contractIdAttr, sumField, "aggs01", "aggs02");
        JSONObject retJson = esHelper.elasticAggs4Buckets2Object(indexTypeEnum, boolQueryBuilder, termsAggregationBuilder);

        //-------------------通过应收款关联的合同id，得到对应币种
        JSONArray jsonArray = retJson.getJSONArray("buckets");
        //合同id--属于该合同的回款单总金额
        Map<Long, Double> cidAmountMap = new HashMap<>(jsonArray.size());
        //可能包含非下属的数据
        Set<Long> contractIdIn = new HashSet<>();
        for (Object object : jsonArray) {
            JSONObject jsonObject = (JSONObject) object;
            //桶内各个合同id
            Long contractId = jsonObject.getLong("key");
            if (contractId == null) {
                continue;
            }
            //该合同对应的金额
            Double value = jsonObject.getJSONObject("aggs02").getDoubleValue("value");

            cidAmountMap.put(contractId, value);
            contractIdIn.add(contractId);
        }
        /*
          （1）前提：有数据
          （2）超管筛选或者非超过角色还需要限制合同负责人（未收款的归属人是合同负责人）
         */
        if (contractIdIn.size() > 0 && chartDtoHelp.judgePermissionAndIfCheck(chartResultPojo)) {
            BoolQueryBuilder contractUserQueryBuilder = boolQuery();
            contractUserQueryBuilder.filter(termQuery("corpid.keyword", corpid));
            contractUserQueryBuilder.filter(termQuery("del", 0));
            contractUserQueryBuilder.filter(termQuery("isMain", 1));
            contractUserQueryBuilder.filter(termsQuery("contractId", contractIdIn));
            contractUserQueryBuilder.filter(termsQuery("userId.keyword", chartResultPojo.getUserIdIn()));
        }
        return convertPaymentAmount(currencyRateMap, corpid, cidAmountMap, contractIdIn);
    }

    /**
     * 获取单个总数、及各个桶内数值list的vo
     * @param elasticAggsPojo 聚合结果pojo
     * @return com.xbongbong.pro.statistic.pojo.vo.ResultCountListVO 结果vo
     * @author zcp
     * @date 2019/2/23 19:29
     * @since v1.0
     * @version v1.0
     */
    public ResultCountListVO getResultCountListVO(ElasticAggsPojo elasticAggsPojo) {
        Integer total = StringUtil.toInt(elasticAggsPojo.getTotal(), 0);
        List<Integer> resultList = new ArrayList<>();
        for (AggsBucketsPojo aggsBucketsPojo : elasticAggsPojo.getBuckets()) {
            resultList.add(StringUtil.toInt(aggsBucketsPojo.getValue(), 0));
        }
        return new ResultCountListVO(total, resultList);
    }

    /**
     * 获取double型总数量，及各个桶内数值list的vo
     * @param elasticAggsPojo 聚合结果pojo
     * @return com.xbongbong.pro.statistic.pojo.vo.ResultDoubleListVO
     * @author zcp
     * @date 2019/2/27 16:56
     * @since v1.0
     * @version v1.0
     */
    public ResultDoubleListVO getResultDoubleListVO(ElasticAggsPojo elasticAggsPojo) {
        Double total = StringUtil.toDouble(elasticAggsPojo.getTotal(), 0D);
        List<Double> resultList = new ArrayList<>();
        for (AggsBucketsPojo aggsBucketsPojo : elasticAggsPojo.getBuckets()) {
            resultList.add(StringUtil.toDouble(aggsBucketsPojo.getValue(), 0D));
        }
        return new ResultDoubleListVO(total, resultList);
    }

    /**
     * 得到count类型统计的聚合结果（只有一层时间分桶聚合）
     * @param chartResultPojo  请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param valueTypeEnum    值类型，用于判断是否处理数组等
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param field            聚合字段，此处为时间分桶字段
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultDetailVO
     * @author zcp
     * @date 2019/2/19 10:04
     * @since v1.0
     * @version v1.0
     */
    public ElasticAggsPojo getCount4Agg(ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum, IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String field) throws XbbException {
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(valueTypeEnum, chartEntity, timeType);
        boolean flag = checkReturnFlag(returnTypeEnum);
        //返回数值的，无需聚合，否则需要置入聚合条件
        ValuesSourceAggregationBuilder dateHistogramAggregation = null;
        if (!flag) {
            //返回数组，需要时间分桶聚合
            dateHistogramAggregation = esHelper.getDateHistogramAggregation(chartResultPojo.getStartTime(), chartResultPojo.getEndTime(), field, timeType);
        }
        /*--------得到聚合结果--------*/
        ElasticAggsPojo elasticAggsPojo = esHelper.elasticAggs(indexTypeEnum, boolQueryBuilder, dateHistogramAggregation);
        //当非数值型，请求自定义时间类型时，把结果处理成一个大桶
        elasticAggsPojo = formatResult4Custom(returnTypeEnum, elasticAggsPojo);
        LOG.info("------------------elasticAggsPojo------------------：" + elasticAggsPojo);
        return elasticAggsPojo;
    }

    //前两个图的数据封装
    public void chartTwoForAnalysis(ChartResultPojo chartResultPojo, JSONArray orderBuckets, JSONArray refundBuckets, List<ResultChartVO> chart) {
        Integer numFormat = chartResultPojo.getNumFormat();
        NumFormatEnum numFormatEnum = NumFormatEnum.getByCode(numFormat);
        String unit = chartResultPojo.getUnit();
        JSONObject dataFormObj = new JSONObject().fluentPut(StringConstant.UNIT, unit);
        ResultChartVO resultChartVO1 = new ResultChartVO();
        resultChartVO1.setName("金额分析");
        resultChartVO1.setTotalFlag(false);
        resultChartVO1.setChartType(4);
        List<ResultDataChartVO> data = new ArrayList<>();
        //先封装第一个图表
        //建3个ResultDataChartVO 封装 订单金额、退货金额、金额合计
        ResultDataChartVO orderAmount = new ResultDataChartVO();
        orderAmount.setName("订单金额");
        orderAmount.setUnit(unit);
        orderAmount.setHide(0);
        orderAmount.setYAxisIndex(0);
        List<String> orderValue = new ArrayList<>();
        orderAmount.setDataForm(dataFormObj);
        ResultDataChartVO refundAmount = new ResultDataChartVO();
        refundAmount.setName("退货金额");
        refundAmount.setUnit(unit);
        refundAmount.setHide(0);
        refundAmount.setYAxisIndex(0);
        List<String> refundValue = new ArrayList<>();
        refundAmount.setDataForm(dataFormObj);
        ResultDataChartVO totalAmount = new ResultDataChartVO();
        totalAmount.setName("金额合计");
        totalAmount.setUnit(unit);
        totalAmount.setHide(0);
        totalAmount.setYAxisIndex(0);
        List<String> totalValue = new ArrayList<>();
        totalAmount.setDataForm(dataFormObj);

        //封装第二个图表
        ResultChartVO resultChartVO2 = new ResultChartVO();
        resultChartVO2.setName("数量分析");
        resultChartVO2.setTotalFlag(false);
        resultChartVO2.setChartType(4);
        List<ResultDataChartVO> data2 = new ArrayList<>();
        //先封装第一个图表
        //建2个ResultDataChartVO 封装 订单数 退货退款数
        ResultDataChartVO orderSum = new ResultDataChartVO();
        orderSum.setName("订单数");
        orderSum.setUnit("个");
        orderSum.setHide(0);
        orderSum.setYAxisIndex(0);
        List<String> orderCount = new ArrayList<>();
        ResultDataChartVO refundSum = new ResultDataChartVO();
        refundSum.setName("退货退款数");
        refundSum.setUnit("个");
        refundSum.setHide(0);
        refundSum.setYAxisIndex(0);
        List<String> refundCount = new ArrayList<>();

        for (int i = 0; i < orderBuckets.size(); i++) {
            JSONObject orderObject = orderBuckets.getJSONObject(i);
            JSONObject refundObject = refundBuckets.getJSONObject(i);
            //把时间放到ResultChartVO中的xAxis中
            BigDecimal orderDecimal = orderObject.getJSONObject("amount").getBigDecimal("value");
            BigDecimal refundDecimal = refundObject.getJSONObject("amount").getBigDecimal("value");
            BigDecimal totalDecimal = orderDecimal.subtract(refundDecimal);
            orderValue.add(ChartResultUtil.formatVal2Thousands(orderDecimal, numFormatEnum));
            refundValue.add(ChartResultUtil.formatVal2Thousands(refundDecimal, numFormatEnum));
            totalValue.add(ChartResultUtil.formatVal2Thousands(totalDecimal, numFormatEnum));
            //第二个图表的数据封装
            orderCount.add(orderObject.getString("doc_count"));
            refundCount.add(refundObject.getString("doc_count"));
        }
        //封装三个指标数据
        orderAmount.setValue(JSONArray.parseArray(JSON.toJSONString(orderValue)));
        data.add(orderAmount);
        refundAmount.setValue(JSONArray.parseArray(JSON.toJSONString(refundValue)));
        data.add(refundAmount);
        totalAmount.setValue(JSONArray.parseArray(JSON.toJSONString(totalValue)));
        data.add(totalAmount);
        resultChartVO1.setData(data);
        resultChartVO1.setXAxis(chartResultPojo.getDateTitleList());
        resultChartVO1.setLegend(Arrays.asList("订单金额","退货金额","金额合计"));
        chart.add(resultChartVO1);
        // TODO 封装第二个图表
        orderSum.setValue(JSONArray.parseArray(JSON.toJSONString(orderCount)));
        data2.add(orderSum);
        refundSum.setValue(JSONArray.parseArray(JSON.toJSONString(refundCount)));
        data2.add(refundSum);
        resultChartVO2.setData(data2);
        resultChartVO2.setXAxis(chartResultPojo.getDateTitleList());
        resultChartVO2.setLegend(Arrays.asList("订单数","退货退款数"));
        chart.add(resultChartVO2);
    }


    /**
     * 返回订单相关的数据
     **/
    public JSONObject orderForSales(ChartResultPojo chartResultPojo) throws XbbException {
        //封装订单状态的条件
        //折线图，时间分桶
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        String fieldTime = "addTime";
        ValuesSourceAggregationBuilder dateHistogramAggregation = esHelper.getDateHistogramAggregation(chartResultPojo.getStartTime(), chartResultPojo.getEndTime(), fieldTime, timeType);
        //订单相关的统计需统计的数据：待发货、待收货、已完成、已关闭 3.4.5.7 （flowStatus不需要了，订单不进审批流
        //查询订货单商品top10、订货单商品数量
        JSONObject orderData = productNum4Distributor(201, chartResultPojo);
        Double orderProductNum = orderData.getDouble("productNum");  //订货商品数量
        JSONObject orderProductData = orderData.getJSONObject("productData");//订货商品数据
        JSONArray orderProductSort = orderData.getJSONArray("productSort");//订货商品排序
        Map<Integer, BigDecimal> orderProductMap = new HashMap();
        List<Integer> orderProductIds = new ArrayList<>();
        for (Object item : orderProductSort) {
            JSONObject jsonObject = JSON.parseObject(item.toString());
            Integer key = jsonObject.getInteger("key");
            Object aggs03 = jsonObject.get("aggs03");
            JSONObject jsonObject1 = JSON.parseObject(aggs03.toString());
            BigDecimal value = jsonObject1.getBigDecimal("value");
            orderProductIds.add(key);
            orderProductMap.put(key, value);
        }
        //查询退货单商品top10、退货单商品数量
        JSONObject refundData = productNum4Distributor(202 , chartResultPojo);
        Double refundProductNum = refundData.getDouble("productNum");  //退货商品数量
        JSONObject refundProductData = refundData.getJSONObject("productData");//退货商品数据
        JSONArray refundProductSort = refundData.getJSONArray("productSort");//退货商品排序
        List<Integer> returnProductIds = new ArrayList<>();
        Map<Integer, BigDecimal> returnProductMap = new HashMap();
        for (Object item : refundProductSort) {
            JSONObject jsonObject = JSON.parseObject(item.toString());
            Integer key = jsonObject.getInteger("key");
            Object aggs03 = jsonObject.get("aggs03");
            JSONObject jsonObject1 = JSON.parseObject(aggs03.toString());
            BigDecimal value = jsonObject1.getBigDecimal("value");
            returnProductIds.add(key);
            returnProductMap.put(key, value);
        }
        BoolQueryBuilder boolQueryBuilder = packageOrderSearch(chartResultPojo);
        //封装经销商名称 查询条件
        List<JSONObject> searchList = chartResultPojo.getSearch();
        if (CollectionsUtil.isNotEmpty(searchList)) {
            for (int i = 0; i < searchList.size(); i++) {
                JSONObject jsonObject = searchList.get(i);
                ConditionsEntityExt condition = JSON.parseObject(jsonObject.toJSONString(), ConditionsEntityExt.class);
                condition.setAttr(OrderEnum.getAttrConnectData(OrderEnum.LINK_DISTRIBUTOR));
                ConditionEnum conditionEnum = ConditionEnum.getConditionEnum(condition.getSymbol());
                EsUtil.symbolChoose4Distributor(boolQueryBuilder, conditionEnum, condition);
            }
        }
        boolQueryBuilder.filter(termQuery(OrderEnum.getAttrConnectData(OrderEnum.IS_RED), 0));
        //订单状态：待发货、待收货、已完成、已关闭。
        boolQueryBuilder.filter(termsQuery(OrderEnum.getAttrConnectData(OrderEnum.STATUS), OrderStatusEnum.getCode4Statistics()));
        //确定索引
        boolQueryBuilder.filter(termQuery(ElasticConstant.MY_JOIN_FIELD,IndexTypeEnum.IDX_SAAS_CONTRACT.getType()));
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //查询条件放到查询语句中
        sourceBuilder.size(0);
        sourceBuilder.query(boolQueryBuilder);
        //合同金额-子聚合  "data.num_1"
        SumAggregationBuilder amountAggBuilder = AggregationBuilders.sum("amount").field(ContractEnum.getAttrConnectData(ContractEnum.AMOUNT));
        dateHistogramAggregation.subAggregation(amountAggBuilder);
        //总的订单金额
        sourceBuilder.aggregation(dateHistogramAggregation);
        SumBucketPipelineAggregationBuilder sumBucket = new SumBucketPipelineAggregationBuilder("aggs02","aggs01>amount");
        sourceBuilder.aggregation(sumBucket);
        //总的订单毛利
        SumAggregationBuilder agg03 = AggregationBuilders.sum("aggs03").field(ContractEnum.getAttrConnectData(ContractEnum.GROSS_PROFIT));
        sourceBuilder.aggregation(agg03);
        //封装语句到SearchRequest中
        SearchRequest searchRequest = new SearchRequest(IndexTypeEnum.IDX_SAAS_CONTRACT.getIndex());
        searchRequest.source(sourceBuilder);
        //执行查询，得到结果
        JSONObject responseObject =  xbbElasticsearchRestTemplate.getAggResponseJson(searchRequest);
        JSONObject aggs = responseObject.getJSONObject("aggregations");
        if (aggs == null) {
            //为空则初始化
            aggs = new JSONObject();
        }
        Long total = 0L;
        if(responseObject.getJSONObject("hits") != null){
            total = responseObject.getJSONObject("hits").getLong("total");
        }
        long totalLong = total == null ? 0L : total;
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("aggregations", aggs);
        jsonObject.put("total", totalLong);

        //此时得到的aggregations不为空，内部已经处理
        JSONObject aggregations = jsonObject.getJSONObject("aggregations");
        //这里的total是订单数
        Long contractTotal = jsonObject.getLong("total");
        JSONArray bucketsArray = aggregations.getJSONObject("aggs01") == null ? new JSONArray() : aggregations.getJSONObject("aggs01").getJSONArray("buckets");
        bucketsArray = bucketsArray == null ? new JSONArray() : bucketsArray;
        // 限制精度
        for (Object object : bucketsArray) {
            JSONObject json = (JSONObject) object;
            //第二层的结果
            Double value = json.getJSONObject("amount").getDoubleValue("value");
            //保留2位小数
            value = Arith.div(value, 1, 2);
            JSONObject amountObject = new JSONObject();
            amountObject.put("value",value);
            json.put("amount",amountObject);
        }
        //其中的amount 为每个日期下的订单金额数据,key_as_string 为单个日期
        // agg03是总的订单毛利
        BigDecimal orderProfit = aggregations.getJSONObject("aggs03").getBigDecimal("value");
        double contractProfit = orderProfit.doubleValue();
        //simple_value#aggs02 是订单金额总计
        Double contractAmount = aggregations.getJSONObject("aggs02") == null ? 0L : aggregations.getJSONObject("aggs02").getDouble("value");

        JSONObject resultJsonObject = new JSONObject();
        resultJsonObject.put("buckets", bucketsArray);
        resultJsonObject.put("total", contractTotal);
        resultJsonObject.put("contractProfit", contractProfit);
        resultJsonObject.put("contractAmount", contractAmount);
        resultJsonObject.put("orderProductNum",orderProductNum);
        resultJsonObject.put("orderProductData",orderProductData);
        resultJsonObject.put("orderProductIds",orderProductIds);
        resultJsonObject.put("orderProductMap",orderProductMap);
        resultJsonObject.put("refundProductNum",Math.abs(refundProductNum));
        resultJsonObject.put("refundProductData",refundProductData);
        resultJsonObject.put("refundProductIds",returnProductIds);
        resultJsonObject.put("returnProductMap",returnProductMap);
        return resultJsonObject;
    }

    private JSONObject productNum4Distributor(int businessType, ChartResultPojo chartResultPojo) throws XbbException {
        BoolQueryBuilder parentBoolQueryBuilder = packageOrderSearch(chartResultPojo);
        //封装经销商名称 查询条件
        List<JSONObject> searchList = chartResultPojo.getSearch();
        if (CollectionsUtil.isNotEmpty(searchList)) {
            for (int i = 0; i < searchList.size(); i++) {
                JSONObject jsonObject = searchList.get(i);
                ConditionsEntityExt condition = JSON.parseObject(jsonObject.toJSONString(), ConditionsEntityExt.class);
                condition.setAttr(OrderEnum.getAttrConnectData(OrderEnum.LINK_DISTRIBUTOR));
                ConditionEnum conditionEnum = ConditionEnum.getConditionEnum(condition.getSymbol());
                EsUtil.symbolChoose4Distributor(parentBoolQueryBuilder, conditionEnum, condition);
            }
        }
        if(Objects.equals(XbbRefTypeEnum.CONTRACT.getCode(), businessType)){
            parentBoolQueryBuilder.filter(termQuery(OrderEnum.getAttrConnectData(OrderEnum.IS_RED), 0));
        }else{
            parentBoolQueryBuilder.filter(termQuery(OrderEnum.getAttrConnectData(OrderEnum.IS_RED), 1));
        }
        String corpid = chartResultPojo.getCorpid();
        //订单状态：待发货、待收货、已完成、已关闭。
        parentBoolQueryBuilder.filter(termsQuery(OrderEnum.getAttrConnectData(OrderEnum.STATUS), OrderStatusEnum.getCode4Statistics()));
        HasParentQueryBuilder hasParentQueryBuilder = JoinQueryBuilders.hasParentQuery(IndexTypeEnum.IDX_SAAS_CONTRACT.getType(), parentBoolQueryBuilder, false);
        BoolQueryBuilder boolQueryBuilder = boolQuery();
        boolQueryBuilder.filter(termQuery("corpid.keyword", corpid));
        boolQueryBuilder.filter(termQuery("del",0));
        boolQueryBuilder.filter(termQuery("my_join_field","my_product"));
        boolQueryBuilder.filter(termQuery("data.num_37",businessType));
        boolQueryBuilder.filter(hasParentQueryBuilder);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(boolQueryBuilder);
        //销售商品总数
        SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("aggs01").field(ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NUM));
        sourceBuilder.aggregation(sumAggregationBuilder);
        //统计
        //两个商品数量排行 TOP10  根据商品id分桶，num来做到排序。
        TermsAggregationBuilder termsAggregationBuilder;
        if(Objects.equals(XbbRefTypeEnum.CONTRACT.getCode(), businessType)){
            termsAggregationBuilder = AggregationBuilders.terms("aggs02").field(ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_ID)).size(10).order(BucketOrder.aggregation("aggs03", false));
        }else{
            termsAggregationBuilder = AggregationBuilders.terms("aggs02").field(ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_ID)).size(10).order(BucketOrder.aggregation("aggs03", true));
        }
        SumAggregationBuilder sumAggregationBuilder1 = AggregationBuilders.sum("aggs03").field(ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NUM));
        termsAggregationBuilder.subAggregation(sumAggregationBuilder1);
        sourceBuilder.aggregation(termsAggregationBuilder);
        //查询哪些数据 产品名称、规格、ID、产品数量
        sourceBuilder.fetchSource(new String[]{
                ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NAME),
                ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.SPECIFICATION),
                ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_ID),
                ContractProductSubFormEnum.getAttrConnectData(ContractProductSubFormEnum.PRODUCT_NUM)},null);
        sourceBuilder.size(PaasConstant.ES_MAX_PAGE_SIZE);
        SearchRequest searchRequest = new SearchRequest(IndexTypeEnum.IDX_SAAS_CONTRACT.getIndex());
        searchRequest.source(sourceBuilder);
        //执行查询，得到结果
        JSONObject responseObject =  xbbElasticsearchRestTemplate.getAggResponse4Product(searchRequest);
        //解析top商品数据
        JSONObject data = responseObject.getJSONObject("data");
        JSONObject aggs = responseObject.getJSONObject("aggregations");
        JSONObject aggs01 = aggs.getJSONObject("aggs01");
        double number = aggs01.getDoubleValue("value");
        JSONObject aggs02 = aggs.getJSONObject("aggs02");
        //返回 退货单商品数量或者订单商品数量、商品的id和信息。
        JSONObject resultObject = new JSONObject();
        resultObject.put("productNum",number);
        resultObject.put("productSort",aggs02.getJSONArray("buckets"));
        resultObject.put("productData",data);
        return resultObject;
    }

    /**
     * @Description //TODO
     * @param
     * @return org.elasticsearch.index.query.BoolQueryBuilder
     * @author weiming
     * @Date  14:05
     * @since
     **/
    public BoolQueryBuilder packageOrderSearch(ChartResultPojo chartResultPojo) throws XbbException {
        //封装开始时间和结束时间，得到boolQueryBuilder
        String fieldTime = "addTime";
        BoolQueryBuilder parentBoolQueryBuilder = chartQueryHelp.orderQuery4Team(chartResultPojo, fieldTime, true);
        //查询formId,封装formIdList
        List<Long> formIdList = paasFormModel.getFormIdList4Distributor(
                XbbRefTypeEnum.CONTRACT.getCode(), chartResultPojo.getCorpid(), 1);
        parentBoolQueryBuilder.filter(termsQuery("formId", formIdList));
        return parentBoolQueryBuilder;
    }

//    public void formatChildSpecification(List<PaasFormDataEntityExt> paasFormDataESList, String corpid) {
//        String specificationAttr = ProductEnum.SPECIFICATION.getAttr();
//        String specificationNameAttr = ProductEnum.SPECIFICATION_LINK_TEXT.getAttr();
//        String specification;
//        for (PaasFormDataEntityExt entityExt : paasFormDataESList){
//            JSONObject data = entityExt.getData();
//            specification = data.getString(specificationAttr);
//            if (specification == null) {
//                continue;
//            }
//            StringBuilder stringBuilder = new StringBuilder();
//            if (Objects.equals(specification,"1")){
//                JSONObject specifition;
//                try {
//                   // specifition = saasUpdateHelp.getJsonObjectOrDefaultFromFormData(data, specificationNameAttr, new JSONObject());
//                } catch (XbbException e) {
//                    // todo 暂时兼容一点脏数据
//                    specifition = new JSONObject();
//                }
//                if (!Objects.isNull(specifition)){
//                    for (Map.Entry entry : specifition.entrySet()){
//                        StringUtil.stringBuilderAppendSuffix(stringBuilder,"/",entry.getValue());
//                    }
//                }
//            } else {
//                specification = data.getString(specificationNameAttr);
//                stringBuilder.append(specification);
//            }
//            data.put(specificationAttr, stringBuilder.toString());
//        }
//    }

    /**
     * 返回退货退款单相关的数据
     **/
    public JSONObject refundForSales(ChartResultPojo chartResultPojo) throws XbbException {
        // TODO 经销商名称 筛选条件
        String fieldTime = "addTime";
        //封装开始时间和结束时间
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.returnQuery4Team(chartResultPojo, fieldTime, true);
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        ValuesSourceAggregationBuilder dateHistogramAggregation = esHelper.getDateHistogramAggregation(chartResultPojo.getStartTime(), chartResultPojo.getEndTime(), fieldTime, timeType);
        List<Long> formIdList = paasFormModel.getFormIdList4Distributor(
                XbbRefTypeEnum.REFUND.getCode(), chartResultPojo.getCorpid(), 1);
        boolQueryBuilder.filter(termsQuery("formId",formIdList));
        //最终把签订人id集合得出来
        //确定索引
        boolQueryBuilder.filter(termQuery(ElasticConstant.MY_JOIN_FIELD,IndexTypeEnum.IDX_SAAS_REFUND.getType()));
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //查询条件放到查询语句中
        sourceBuilder.size(0);
        sourceBuilder.query(boolQueryBuilder);
        //退货金额-子聚合
        SumAggregationBuilder amountAggBuilder = AggregationBuilders.sum("amount").field("data.num_1");
        dateHistogramAggregation.subAggregation(amountAggBuilder);
        //总的退货金额
        sourceBuilder.aggregation(dateHistogramAggregation);
        SumBucketPipelineAggregationBuilder sumBucket = new SumBucketPipelineAggregationBuilder("aggs02","aggs01>amount");
        sourceBuilder.aggregation(sumBucket);
        //封装语句到SearchRequest中
        SearchRequest searchRequest = new SearchRequest(IndexTypeEnum.IDX_SAAS_REFUND.getIndex());
        searchRequest.source(sourceBuilder);
        //执行查询，得到结果
        JSONObject responseObject =  xbbElasticsearchRestTemplate.getAggResponseJson(searchRequest);
        JSONObject aggs = responseObject.getJSONObject("aggregations");
        if (aggs == null) {
            //为空则初始化
            aggs = new JSONObject();
        }
        Long total = 0L;
        if(responseObject.getJSONObject("hits") != null){
            total = responseObject.getJSONObject("hits").getLong("total");
        }
        long totalLong = total == null ? 0L : total;
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("aggregations", aggs);
        jsonObject.put("total", totalLong);

        //此时得到的aggregations不为空，内部已经处理
        JSONObject aggregations = jsonObject.getJSONObject("aggregations");
        //这里的total是退货单数
        Long refundTotal = jsonObject.getLong("total");
        JSONArray bucketsArray = aggregations.getJSONObject("aggs01") == null ? new JSONArray() : aggregations.getJSONObject("aggs01").getJSONArray("buckets");
        bucketsArray = bucketsArray == null ? new JSONArray() : bucketsArray;
        //其中的amount 为每个日期下的订单金额数据,key_as_string 为单个日期
        //simple_value#aggs02 是订单金额总计
        Double refundAmount = aggregations.getJSONObject("aggs02") == null ? 0L : aggregations.getJSONObject("aggs02").getDouble("value");
        JSONObject resultJsonObject = new JSONObject();
        resultJsonObject.put("buckets", bucketsArray);
        resultJsonObject.put("total", refundTotal);
        resultJsonObject.put("refundAmount", refundAmount);
        return resultJsonObject;
    }
    /**
     * 得到int类型字段求和
     * @param chartResultPojo  请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param valueTypeEnum    值类型，用于判断是否处理数组等
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param fieldTime        聚合字段，此处为时间分桶字段
     * @param sumField         求和的金额字段
     * @return com.xbongbong.pro.statistic.result.pojo.ElasticAggsPojo
     * @author zcp
     * @date 2019/6/3 17:47
     * indexTypeEnum 核对完成 已更改为indexModel中方法 @date 2022/5/6 15:35
     * @since v1.0
     * @version v1.0
     */
    public ElasticAggsPojo getInt4Agg(ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum, IndexTypeEnum indexTypeEnum,
                                      BoolQueryBuilder boolQueryBuilder, String fieldTime, String sumField) throws XbbException {
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(valueTypeEnum, chartEntity, timeType);
        boolean flag = checkReturnFlag(returnTypeEnum);
        //返回数值的，无需聚合，否则需要置入聚合条件
        if (flag) {
            TotalAndAggPojo totalAndAggPojo = getInt4Total(indexTypeEnum, boolQueryBuilder, sumField);
            int agg = totalAndAggPojo.getAgg();
            return formatResult4Total(returnTypeEnum, String.valueOf(agg));
        } else {
            //返回数组，需要时间分桶聚合
            ValuesSourceAggregationBuilder dateHistogramAggregation = esHelper.getDateHistogramAggregation(chartResultPojo.getStartTime(), chartResultPojo.getEndTime(), fieldTime, timeType);
            SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("aggs02").field(sumField);
            dateHistogramAggregation.subAggregation(sumAggregationBuilder);
            /*--------得到聚合结果--------*/
            return esHelper.elasticAggs4Int(indexTypeEnum, boolQueryBuilder, dateHistogramAggregation);
        }
    }

    /**
     * 得到Double类型（通话记录时间）统计的聚合结果（两层或三层聚合）
     * 当返回总数时，双层聚合：币种分组、金额求和
     * 当返回数组，三层聚合：时间分桶、通话记录时间求和
     * @param chartResultPojo  请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param valueTypeEnum    值类型，用于判断是否处理数组等
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param fieldTime        聚合字段，此处为时间分桶字段
     * @param sumField         求和的金额字段
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author youli.chen
     * @date 2019/2/27 15:50
     * indexTypeEnum 核对完成 已更改为indexModel中方法 @date 2022/5/6 15:35
     * @since v1.0
     * @version v1.0
     */
    public ElasticAggsPojo getDouble4Agg(ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum, IndexTypeEnum indexTypeEnum,
                                         BoolQueryBuilder boolQueryBuilder, String fieldTime, String sumField) throws XbbException {
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        Integer numFormat = chartResultPojo.getNumFormat();
        NumFormatEnum numFormatEnum = NumFormatEnum.getByCode(numFormat);
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(valueTypeEnum, chartEntity, timeType);
        boolean flag = checkReturnFlag(returnTypeEnum);
        //返回数值的，无需聚合，否则需要置入聚合条件
        if (flag) {
            double total = getDouble4Total(indexTypeEnum, boolQueryBuilder, sumField);
            return formatResult4Total(returnTypeEnum, String.valueOf(total));
        } else {
            //返回数组，需要时间分桶聚合
            ValuesSourceAggregationBuilder dateHistogramAggregation = esHelper.getDateHistogramAggregation(chartResultPojo.getStartTime(), chartResultPojo.getEndTime(), fieldTime, timeType);
            SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("aggs02").field(sumField);
            dateHistogramAggregation.subAggregation(sumAggregationBuilder);
            /*--------得到聚合结果--------*/
            ElasticAggsPojo elasticAggsPojo = esHelper.elasticAggs4Double(indexTypeEnum, boolQueryBuilder, dateHistogramAggregation);
            for (AggsBucketsPojo bucket : elasticAggsPojo.getBuckets()) {
                // 处理数值格式
                BigDecimal formatValue = ChartResultUtil.formatVal4Num(new BigDecimal(bucket.getValue()), numFormatEnum);
                bucket.setValue(String.valueOf(formatValue));
            }
            // 总计处理数值格式
            BigDecimal totalFormatValue = ChartResultUtil.formatVal4Num(new BigDecimal(elasticAggsPojo.getTotal()), numFormatEnum);
            elasticAggsPojo.setTotal(String.valueOf(totalFormatValue));
            chartResultPojo.setNumFormatFlag(true);
            return elasticAggsPojo;
        }
    }


    /**
     * 统计结果得到后直接封装好内层结果，到最终返回时直接读取使用：只按照时间分桶塞入n个map，每个map内封装数量、总金额的显示信息
     * 注：
     *      两层或三层聚合
     *      当返回总数时，双层聚合：terms分组、sum求和
     *      当返回数组，三层聚合：时间分桶、terms分组、sum求和
     * @param chartResultPojo  请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param valueTypeEnum    值类型，用于判断是否处理数组等
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param aggMorePojo
     * @return java.util.List<java.util.Map<java.lang.String,com.xbongbong.pro.statistic.result.pojo.StageFunnelInfoPojo>>
     * @since v1.0
     * @version v1.0
     */
    public List<Map<String, StageFunnelInfoPojo>> getAgg4TimeAndField(ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum, IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, AggMorePojo aggMorePojo) throws XbbException {
        //与时间分桶的大小一样（如果不需时间分桶<自定义区间筛选时，一个总时间维度不需时间分桶>，则就一个）
        List<Map<String, StageFunnelInfoPojo>> list = new ArrayList<>();
        String aggs1 = aggMorePojo.getAggs();
        String aggs2 = aggMorePojo.getAggs2();
        String aggs3 = aggMorePojo.getAggs3();
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        ThroughDTO throughDTO = new ThroughDTO(chartResultPojo, chartResultPojo.getSystemCodeEnum());
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(valueTypeEnum, chartEntity, timeType);
        String valueStr = BasicConstant.VALUE;
        boolean flag = checkReturnFlag(returnTypeEnum);
        //无需时间聚合，否则需要置入时间分桶条件
        if (flag) {
            //不用时间分桶，只terms + sum聚合
            TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms(aggs1).field(aggMorePojo.getAggsTermsField());
            termsAggregationBuilder.size(aggMorePojo.getAggsTermsSize());
            SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum(aggs2).field(aggMorePojo.getAggsSumField());
            termsAggregationBuilder.subAggregation(sumAggregationBuilder);
            JSONObject resultJsonObject = esHelper.elasticAggs4Buckets2Object(indexTypeEnum, boolQueryBuilder, termsAggregationBuilder);
            TimePojo timePojo = throughHelp.formatThroughTimeInfo(false, chartResultPojo.getFiscalYearTimePojo(), chartResultPojo.getStartTime(), chartResultPojo.getEndTime());
            Map<String, StageFunnelInfoPojo> termsSumMap = formatResult4TimeAndField(chartResultPojo, resultJsonObject, aggs2, valueStr, throughDTO, timePojo);
            list.add(termsSumMap);
        } else {
            //第一层时间分桶，第二层terms分桶，第三层sum聚合
            ValuesSourceAggregationBuilder dateHistogramAggregation = esHelper.getDateHistogram4TermAndSum(chartResultPojo.getStartTime(), chartResultPojo.getEndTime(), timeType, aggMorePojo);
            /*--------得到聚合结果--------*/
            JSONObject resultJsonObject = esHelper.elasticAggs4Buckets2Object(indexTypeEnum, boolQueryBuilder, dateHistogramAggregation);
            JSONArray bucketsArray = resultJsonObject.getJSONArray("buckets");
            Map<String, StageFunnelInfoPojo> defaultMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            //处理穿透
            List<Integer> startTimeList = chartResultPojo.getStartTimeList();
            List<Integer> endTimeList = chartResultPojo.getEndTimeList();
            boolean splitFlag = true;
            for (int i = 0; i < bucketsArray.size(); i++) {
                JSONObject json = bucketsArray.getJSONObject(i);
                JSONObject jsonObject = json.getJSONObject(aggs2);
                if (Objects.isNull(jsonObject)) {
                    //该时间分桶无数据，就塞入一个空的
                    list.add(defaultMap);
                    continue;
                }
                TimePojo timePojo = throughHelp.formatThroughTimeInfo(splitFlag, fiscalYearTimePojo, startTimeList.get(i), endTimeList.get(i));
                Map<String, StageFunnelInfoPojo> subTermsSumMap = formatResult4TimeAndField(chartResultPojo, jsonObject, aggs3, valueStr, throughDTO, timePojo);
                list.add(subTermsSumMap);
            }
        }
        return list;
    }

    /**
     * 单个产品销量
     * 聚合条件封装+获取结果
     *
     * @param chartResultPojo  入参
     * @param fieldTime        时间筛选字段
     * @param sumField         求和字段
     * @param childType        子表type
     * @param indexTypeEnum    主表的index和type
     * @param boolQueryBuilder 筛选参数
     * @param aggsProductSalesQuery 用子产品聚合时要塞入的条件
     * @return com.xbongbong.pro.statistic.result.pojo.ElasticAggsPojo
     * @author 徐俊杰
     * @date 2019/4/26 9:05
     * @since v1.0
     */
    public ElasticAggsPojo getAgg4ProductSales(ChartResultPojo chartResultPojo, String fieldTime, String termsField, String sumField, String childType,
                                               IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, BoolQueryBuilder aggsProductSalesQuery) throws XbbException {
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        //时间聚合
        ValuesSourceAggregationBuilder dateHistogramAggregation = esHelper.getDateHistogramAggregation(chartResultPojo.getStartTime(),
                chartResultPojo.getEndTime(), fieldTime, timeType);
        //设置children
        ChildrenAggregationBuilder aggs02 = JoinAggregationBuilders.children("aggs02", childType);
        //塞入子文档的条件（如果不塞入会把父文档下的所有子文档进行sum，所以要在聚合条件内塞入：合同下面所有的产品都汇总）
        FilterAggregationBuilder aggs03 = AggregationBuilders.filter("aggs03", aggsProductSalesQuery);
        //求和
        SumAggregationBuilder sumAgg = AggregationBuilders.sum("aggs04").field(sumField);
        aggs03.subAggregation(sumAgg);
        aggs02.subAggregation(aggs03);
        dateHistogramAggregation.subAggregation(aggs02);
        Long productId = chartResultPojo.getRefId();
        return esHelper.elasticAggs4Three(indexTypeEnum, boolQueryBuilder, dateHistogramAggregation, productId);
    }

    /**
     * 首页库存成本聚合获取（通过script对两个字段相乘）
     * @param boolQueryBuilder 库存成本查询条件
     * @return double
     * @throws XbbException
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    public double getAgg4ProductCost(BoolQueryBuilder boolQueryBuilder) throws XbbException {
        Script script = ScriptHelp.scriptMultiply(ProductEnum.getAttrConnectData(ProductEnum.COST), ProductEnum.getAttrConnectData(ProductEnum.STOCK));
        boolQueryBuilder.filter(existsQuery(ProductEnum.getAttrConnectData(ProductEnum.COST)));
        boolQueryBuilder.filter(existsQuery(ProductEnum.getAttrConnectData(ProductEnum.STOCK)));
        SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("cost").script(script);
        JSONObject jsonObject = esHelper.elasticAggs4NoBuckets2Object(IndexTypeEnum.IDX_SAAS_PRODUCT, boolQueryBuilder, sumAggregationBuilder);
        JSONObject aggregations = jsonObject.getJSONObject("aggregations");
        Double value = aggregations.getJSONObject("cost").getDouble("value");
        //保留2位小数
        double total = Arith.div(value, 1, 2);
        return total;
    }

    /**
     * 废弃原因，pro无多币种
     * 得到amount类型（带多币种）统计的聚合结果（两层或三层聚合）
     * 当返回总数时，双层聚合：币种分组、金额求和
     * 当返回数组，三层聚合：时间分桶、币种分组、金额求和
     * @param chartResultPojo  请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param valueTypeEnum    值类型，用于判断是否处理数组等
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param fieldTime        聚合字段，此处为时间分桶字段
     * @param currencyField    要聚合的币种字段
     * @param sumField         求和的金额字段
     * @param currencyRateMap  币种id--币种汇率
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author zcp
     * @date 2019/2/27 15:50
     * indexTypeEnum 核对完成 已更改为indexModel中方法 @date 2022/5/6 15:35
     * @since v1.0
     * @version v1.0
     */
    @Deprecated
    public ElasticAggsPojo getAmount4Agg(ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum, IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String fieldTime, String currencyField, String sumField, Map<Long, Double> currencyRateMap) throws XbbException {
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(valueTypeEnum, chartEntity, timeType);
        boolean flag = checkReturnFlag(returnTypeEnum);
        //返回数值的，无需聚合，否则需要置入聚合条件
        if (flag) {
            double total = getAmount4Total(indexTypeEnum, boolQueryBuilder, currencyField, sumField, currencyRateMap);
            return formatResult4Total(returnTypeEnum, String.valueOf(total));
        } else {
            //返回数组，需要时间分桶聚合
            ValuesSourceAggregationBuilder dateHistogramAggregation = esHelper.getDateHistogramAggregation(chartResultPojo.getStartTime(), chartResultPojo.getEndTime(), fieldTime, timeType);
            TermsAggregationBuilder termsAggregationBuilder = esHelper.getTermsAggregation4Two(currencyField, sumField, "aggs02", "aggs03");
            dateHistogramAggregation.subAggregation(termsAggregationBuilder);
            /*--------得到聚合结果--------*/
            return esHelper.elasticAggs4Amount(indexTypeEnum, boolQueryBuilder, dateHistogramAggregation, currencyRateMap);
        }
    }

    /**
     * 该方法暂时废弃，原因：该方法为三层聚合（其中一层为币种id分桶），pro多币种不做，因此方法暂时废弃---2019-4-6
     * 合同金额客户排名
     * 三层聚合：客户id、币种、金额
     * @param chartResultPojo     请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @param boolQueryBuilder    查询条件
     * @param currencyRateMap     币种id--币种汇率
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultDetailVO
     * @throws XbbException
     * @author zcp
     * @date 2019/2/28 23:12
     * @since v1.0
     * @version v1.0
     */
    @Deprecated
    public ChartResultDetailVO getTopCustomer4ContractCurrency(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO, BoolQueryBuilder boolQueryBuilder, Map<Long, Double> currencyRateMap) throws XbbException {
        String corpid = chartResultPojo.getCorpid();
        String termsField = ContractEnum.getEsAttr4Keyword(ContractEnum.LINK_CUSTOMER);
//        String currencyField = ContractEnum.getEsAttr4Keyword(ContractEnum.UNIT);
        String sumField = ContractEnum.getEsAttr4Keyword(ContractEnum.AMOUNT);
        //三层聚合
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("aggs01").field(termsField);
        termsAggregationBuilder.size(Integer.MAX_VALUE);
        TermsAggregationBuilder subTermsAggregationBuilder = esHelper.getTermsAggregation4Two("currencyField", sumField, "aggs02", "aggs03");
        termsAggregationBuilder.subAggregation(subTermsAggregationBuilder);

        JSONObject resultJsonObject = esHelper.elasticAggs4Buckets2Object(IndexTypeEnum.IDX_SAAS_CONTRACT, boolQueryBuilder, termsAggregationBuilder);
        JSONArray bucketsArray = resultJsonObject.getJSONArray("buckets");
        //客户id--金额
        Map<Long, Double> amountCustomerMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        for (Object object : bucketsArray) {
            if (object != null) {
                JSONObject json = (JSONObject) object;
                Long customerId = json.getLong("key");
                if (customerId == null) {
                    //客户不存在，跳过
                    continue;
                }
                //第二层的结果
                JSONArray jsonArrayTwo = json.getJSONObject("aggs02").getJSONArray("buckets");
                //每个桶内（各个币种）内总金额（转为人民币）
                double perAmount = 0;
                for (Object objectTwo : jsonArrayTwo) {
                    double amount = esHelper.getBucketsAmount(currencyRateMap, objectTwo, "aggs03");
                    perAmount += amount;
                }
                //保留2位小数
                perAmount = Arith.div(perAmount, 1, 2);
                amountCustomerMap.put(customerId, perAmount);
            }
        }
        //customerId--全公司排名值
        Map<Long, Integer> corpRankMapTop10 = SortHelp.sortMapValueDouble4KeyLong(amountCustomerMap, SortHelp.TOP_10);

        /*--------得到聚合结果--------*/
        String seriesName = I18nMessageUtil.getMessage(I18nStringConstant.CONTRACT_AMOUNT);
        return reuslt4CustomerTop(seriesName, corpid, corpRankMapTop10, amountCustomerMap, chartResultPojo, chartResultDetailVO);
    }

    /**
     * 该方法暂时废弃，原因：该方法为三层聚合（其中有处理币种），pro多币种不做，因此方法暂时废弃---2019-4-6
     * 回款金额客户排名
     * 三层聚合：客户id、币种、金额
     * @param chartResultPojo     请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @param boolQueryBuilder    查询条件
     * @param currencyRateMap     币种id--币种汇率
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultDetailVO
     * @throws XbbException
     * @author zcp
     * @date 2019/3/1 1:13
     * @since v1.0
     * @version v1.0
     */
    @Deprecated
    public ChartResultDetailVO getTopCustomer4Payment(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO, BoolQueryBuilder boolQueryBuilder, Map<Long, Double> currencyRateMap) throws XbbException {
        String corpid = chartResultPojo.getCorpid();
        //先双层聚合：合同id、金额
        String termsField = PaymentSheetEnum.getEsAttr4Keyword(PaymentSheetEnum.CONTRACT);
        String sumField = PaymentSheetEnum.getEsAttr4Keyword(PaymentSheetEnum.AMOUNT);
        //返回数组，需要时间分桶聚合
        TermsAggregationBuilder termsAggregationBuilder = esHelper.getTermsAggregation4Two(termsField, sumField, "aggs01", "aggs02");
        JSONObject resultJsonObject = esHelper.elasticAggs4Buckets2Object(IndexTypeEnum.IDX_SAAS_PAYMENT_SHEET, boolQueryBuilder, termsAggregationBuilder);
        JSONArray bucketsArray = resultJsonObject.getJSONArray("buckets");

        //--------------先聚合得到各个合同及其对应的总金额（其自身币种的金额，未做币种转化）
        //合同id--该合同对应的金额
        Map<Long, Double> cidAmountMap = new HashMap<>(bucketsArray.size());
        Set<Long> contractIdIn = new HashSet<>();
        for (Object object : bucketsArray) {
            if (object != null) {
                JSONObject json = (JSONObject) object;
                //桶内各个合同id
                Long contractId = json.getLong("key");
                if (contractId == null) {
                    continue;
                }
                //该合同对应的金额
                Double value = json.getJSONObject("aggs02").getDoubleValue("value");
                cidAmountMap.put(contractId, value);
                contractIdIn.add(contractId);
            }
        }
        //--------------币种转化，以得到各个合同对应的人民币金额
        //聚合出各个桶内的合同ids对应的合同实体list
        List<ContractInfoVO> contractInfoVOList = formHelp.getContractListByIds(corpid, contractIdIn);
        //合同id--币种汇率对应关系map
        Map<Long, Double> cidRateMap = getExchangeRateMapFromContract(contractInfoVOList, currencyRateMap);
        //合同id--该合同对应的人民币金额
        Map<Long, Double> amountContractMap = new HashMap<>(cidAmountMap.size());
        //每个桶内（各个合同）内总金额（转为人民币）
        for (Entry<Long, Double> entry : cidAmountMap.entrySet()) {
            double amount = getAmount4Payment(cidRateMap, entry);
            amountContractMap.put(entry.getKey(), amount);
        }
        //--------------然后汇总客户的所有合同的金额（即同个客户下的合同金额汇集到一起）
        Map<Long, Double> amountCustomerMap = new HashMap<>(bucketsArray.size());
        //把一个客户下所有合同的金额汇集到一起
        for (ContractInfoVO detailVO : contractInfoVOList) {
            Long customerId = detailVO.getCustomerId();
            Double perTotal = amountCustomerMap.get(customerId);
            if (perTotal == null) {
                perTotal = 0D;
            }
            Long contractId = detailVO.getId();
            Double cAmount = amountContractMap.get(contractId);
            perTotal += cAmount;
            //for循环内不做保留两位小数操作
            amountCustomerMap.put(customerId, perTotal);
        }
        //此时再保留两位小数
        for (Entry<Long, Double> entry : amountCustomerMap.entrySet()) {
            //保留2位小数
            Double amount = Arith.div(entry.getValue(), 1, 2);
            amountCustomerMap.put(entry.getKey(), amount);
        }

        //customerId--全公司排名值
        Map<Long, Integer> corpRankMapTop10 = SortHelp.sortMapValueDouble4KeyLong(amountCustomerMap, SortHelp.TOP_10);
        /*--------得到聚合结果--------*/
        String seriesName = I18nMessageUtil.getMessage(I18nStringConstant.PAYMENT_AMOUNT);
        return reuslt4CustomerTop(seriesName, corpid, corpRankMapTop10, amountCustomerMap, chartResultPojo, chartResultDetailVO);
    }

    /**
     * 封装前TOP_TEN排行结果（还需要处理id和名称的对应关系map）
     * @param indexTypeEnum    聚合业务的索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param termField        分桶聚合字段
     * @param sumField         求和字段
     * @param isReturn         是否是退货
     * @return com.xbongbong.pro.statistic.result.pojo.TopResultPojo
     * @author zcp
     * @date 2019/4/6 18:51
     * @since v1.0
     * @version v1.0
     */
    public TopResultPojo getTopResultPojo4Base(IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String termField, String sumField,Boolean isReturn) throws XbbException {
        //以termField字段 terms聚合，然后以数量sum：根据第二层的sum结果降序排列，获取前10个产品（分桶）
        String aggsSub = "aggs02";
        //按照第二层sum结果降序
        BucketOrder order = null;
        if (null != isReturn && isReturn){
            order = BucketOrder.aggregation(aggsSub, isReturn);
        }else {
            order = BucketOrder.aggregation(aggsSub, false);
        }        TermsAggregationBuilder termsAggregationBuilder = esHelper.getTermsAggregationOrder4Two(ChartConstant.TOP_TEN, order, termField, sumField, "aggs01", aggsSub);
        ElasticAggsPojo elasticAggsPojo = esHelper.elasticAggs4Double(indexTypeEnum, boolQueryBuilder, termsAggregationBuilder);
        List<AggsBucketsPojo> aggsBucketsPojoList = elasticAggsPojo.getBuckets();
        //idList（聚合时已经排序）
        List<Long> idList = new ArrayList<>();
        for (AggsBucketsPojo bucket : aggsBucketsPojoList) {
            //termField对应的值，一般为id
            idList.add(StringUtil.StringToLong(bucket.getKey(), 0L));
        }
        //置入TopResultPojo
        TopResultPojo topResultPojo = new TopResultPojo();
        topResultPojo.setIdList(idList);
        topResultPojo.setAggsBucketsPojoList(aggsBucketsPojoList);
        return topResultPojo;
    }

    /**
     * 封装客户排行（合同金额、回款金额 客户排名）需要的所有信息
     * @param indexTypeEnum    索引枚举
     * @param corpid           公司id
     * @param boolQueryBuilder 查询条件
     * @param termsField       分组字段
     * @param sumField         求和字段
     * @return com.xbongbong.pro.statistic.result.pojo.TopResultPojo
     * @throws XbbException
     * @author zcp
     * @date 2019/7/5 15:13
     * @since v1.0
     * @version v1.0
     */
    public TopResultPojo getTopResultPojo4CustomerTop(IndexTypeEnum indexTypeEnum, String corpid, BoolQueryBuilder boolQueryBuilder, String termsField, String sumField) throws XbbException {
        //排行过合同金额，要需要查询合同关联的客户名称，即要查客户表单
        CustomerManagementEnum nameEnum = CustomerManagementEnum.NAME;
        List<String> fieldList = Arrays.asList("id", "dataId", CustomerManagementEnum.getAttrConnectData(nameEnum));
        //以合同关联的客户id terms聚合，然后以金额sum：根据第二层的sum结果降序排列，获取前10个合同（分桶）
        TopResultPojo topResultPojo = getTopResultPojo4Base(indexTypeEnum, boolQueryBuilder, termsField, sumField,false);
        //拼装id-名称map，并置入topResultPojo
        IndexTypeEnum customerIndex =  indexTypeModel.getCustomerIndex(corpid, XbbRefTypeEnum.CUSTOMER_MANAGEMENT.getCode(), SaasMarkEnum.SAAS.getCode());
        Map<Long, String> idNameMap = formHelp.getFormIdAttrMapByIds4All(nameEnum.getAttr(), corpid, customerIndex, topResultPojo.getIdList(), fieldList);
        topResultPojo.setIdNameMap(idNameMap);
        return topResultPojo;
    }

    /**
     * 封装产品排行（产品名称需要拼上产品的规格信息）需要的所有信息
     * @param corpid           公司id
     * @param boolQueryBuilder 查询条件
     * @param termsField       分组字段
     * @param sumField         求和字段
     * @return com.xbongbong.pro.statistic.result.pojo.TopResultPojo
     * @throws XbbException
     * @author zcp
     * @date 2019/7/5 15:47
     * @since v1.0
     * @version v1.0
     */
    public TopResultPojo getTopResultPojo4ProductTop(String corpid, BoolQueryBuilder boolQueryBuilder, String termsField, String sumField,Boolean isReturn) throws XbbException {
        //以产品id terms聚合，然后以数量sum：根据第二层的sum结果降序排列，获取前10个产品（分桶）
        TopResultPojo topResultPojo = getTopResultPojo4Base(IndexTypeEnum.IDX_SAAS_CONTRACT_PRODUCT, boolQueryBuilder, termsField, sumField,isReturn);
        //拼装id-产品信息map，并置入topResultPojo
        Map<Long, String> idInfoMap = formHelp.getProductIdInfoMapByIds(corpid, topResultPojo.getIdList());
        topResultPojo.setIdNameMap(idInfoMap);
        return topResultPojo;
    }

    /**
     * 封装产品销量排行榜信息
     *
     * @param chartResultPojo     图相关信息
     * @param chartResultDetailVO 结果
     * @param topResultPojo       用于产品top10、合同客户top10、回款客户top10的pojo
     * @param seriesName          柱状图上面的柱内的名称
     * @return com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO
     * @author 徐俊杰
     * @date 2019/4/2 15:01
     * @since v1.0
     */
    public ChartResultDetailVO result4Top(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO, TopResultPojo topResultPojo, String seriesName) {
        //图表封装
        List<ResultChartVO> resultChartVOList = new ArrayList<>();
        ResultChartVO resultChartVO = new ResultChartVO();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        String unit = null;
        if (chartEntity != null) {
            //如果图信息存在，则把图相关信息置入chartResultChartVO
            BeanUtil.copyProperties(chartEntity, resultChartVO);
            unit = chartEntity.getUnit();
        }
        // 数值格式化
        JSONObject dataFormObj = new JSONObject();
        dataFormObj.put(StringConstant.UNIT, unit);
        NumFormatEnum numFormatEnum = NumFormatEnum.getByCode(chartResultPojo.getNumFormat());

        List<ResultDataChartVO> data = new ArrayList<>();
        JSONArray valueArray = new JSONArray();
        List<String> xAxis = new ArrayList<>();
        Map<Long, String> idNameMap = topResultPojo.getIdNameMap();
        for (AggsBucketsPojo bucket : topResultPojo.getAggsBucketsPojoList()) {
            //数量/金额
            String value = ChartResultUtil.formatValue2String(true, chartResultPojo.getValueTypeEnum(), false, bucket.getValue(), numFormatEnum, chartResultPojo.getNumFormatFlag());
            valueArray.add(value);
            //名称
            String name = idNameMap.get(StringUtil.StringToLong(bucket.getKey()));
            name = StringUtil.isEmpty(name) ? I18nMessageUtil.getMessage(I18nStringConstant.UNKNOW) : name;
            xAxis.add(name);
        }
        ResultDataChartVO resultDataChartVO = new ResultDataChartVO(seriesName, unit, valueArray, dataFormObj);
        data.add(resultDataChartVO);
        resultChartVO.setData(data);
        resultChartVO.setXAxis(xAxis);
        resultChartVOList.add(resultChartVO);
        chartResultDetailVO.setChart(resultChartVOList);
        return chartResultDetailVO;
    }

    /**
     * 封装排行结果（已经封装出总数、及名称map，无需聚合也无需查名称）
     * @param seriesName          柱状图上面的柱内的名称
     * @param corpRankMap         id--排名值（客户金额）
     * @param idValueMap          id--总额
     * @param idNameMap           id--名称
     * @param chartResultPojo     图相关信息
     * @param chartResultDetailVO 结果
     * @return com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO
     * @throws XbbException
     * @author zcp
     * @date 2019/4/9 16:24
     * @since v1.0
     * @version v1.0
     */
    public ChartResultDetailVO result4Top(String seriesName, Map<Long, Integer> corpRankMap, Map<Long, Double> idValueMap, Map<Long, String> idNameMap, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        NumFormatEnum numFormatEnum = NumFormatEnum.getByCode(chartResultPojo.getNumFormat());
        //存放金额数组
        JSONArray valueArray = new JSONArray();
        //先循环获取出客户id、各个客户总金额
        for (Entry<Long, Integer> entry : corpRankMap.entrySet()) {
            String value = ChartResultUtil.formatValue2Double(true, chartResultPojo.getValueTypeEnum(), false, idValueMap.get(entry.getKey()), numFormatEnum, chartResultPojo.getNumFormatFlag());
            valueArray.add(value);
        }
        //图的data，因top排行图上面只有一条线，固list只需要一个子
        List<ResultDataChartVO> data = new ArrayList<>();
        ResultDataChartVO resultDataChartVO = new ResultDataChartVO(seriesName, null, valueArray);
        data.add(resultDataChartVO);
        /*--------封装最后结果--------*/
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        //声明图和表的信息VO
        List<ResultChartVO> resultChartVOList = new ArrayList<>();
        //--------封装一个图resultChartVO的信息
        ResultChartVO resultChartVO = new ResultChartVO();
        if (chartEntity != null) {
            //如果图信息存在，则把图相关信息置入chartResultChartVO
            BeanUtil.copyProperties(chartEntity, resultChartVO);
        }
        resultChartVO.setData(data);
        List<String> xAxis = new ArrayList<>();
        for (Entry<Long, Integer> entry : corpRankMap.entrySet()) {
            String name = idNameMap.get(entry.getKey());
            name = StringUtil.isEmpty(name) ? I18nMessageUtil.getMessage(I18nStringConstant.UNKNOW) : name;
            xAxis.add(name);
        }
        resultChartVO.setXAxis(xAxis);
        resultChartVOList.add(resultChartVO);
        //图相关塞入最终结果chartResultDetailVO内
        chartResultDetailVO.setChart(resultChartVOList);
        return chartResultDetailVO;
    }

    /**
     * 废弃原因：pro无多币种
     * 单独为求回款金额的统计聚合结果（两层或三层聚合，币种依托合同的币种）
     * 当返回总数时，双层聚合：合同id分组、金额求和
     * 当返回数组，三层聚合：时间分桶、合同id分组、金额求和
     * @param chartResultPojo  请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param valueTypeEnum    返回值类型
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param fieldTime        聚合字段，此处为时间分桶字段
     * @param sumField         求和的金额字段
     * @param currencyRateMap  币种id--币种汇率
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @throws XbbException
     * @author zcp
     * @date 2019/2/28 18:35
     * @since v1.0
     * @version v1.0
     */
    @Deprecated
    public ElasticAggsPojo getPayment4Agg(ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum, IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String fieldTime, String sumField, Map<Long, Double> currencyRateMap) throws XbbException {
        String corpid = chartResultPojo.getCorpid();
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(valueTypeEnum, chartEntity, timeType);
        boolean flag = checkReturnFlag(returnTypeEnum);
        //返回数值的，无需聚合，否则需要置入聚合条件
        if (flag) {
            //该方法内部双层聚合：合同id、金额
            double total = getPayment4Total(indexTypeEnum, chartResultPojo, boolQueryBuilder, sumField, currencyRateMap);
            return formatResult4Total(returnTypeEnum, String.valueOf(total));
        } else {
            //先以时间分组，然后必须按照合同id分组，以获取合同币种id进行处理
            String contractIdAttr = PaymentSheetEnum.getEsAttr4Keyword(PaymentSheetEnum.CONTRACT);
            //返回数组，需要时间分桶聚合
            ValuesSourceAggregationBuilder dateHistogramAggregation = esHelper.getDateHistogramAggregation(chartResultPojo.getStartTime(), chartResultPojo.getEndTime(), fieldTime, timeType);
            TermsAggregationBuilder termsAggregationBuilder = esHelper.getTermsAggregation4Two(contractIdAttr, sumField, "aggs02", "aggs03");
            dateHistogramAggregation.subAggregation(termsAggregationBuilder);
            JSONObject resultJsonObject = esHelper.elasticAggs4Buckets2Object(indexTypeEnum, boolQueryBuilder, dateHistogramAggregation);
            JSONArray bucketsArray = resultJsonObject.getJSONArray("buckets");
            //存放各个时间段内的合同和金额的对应关系（然后通过合同id去查合同，以处理币种）：合同id--属于该合同的回款单总金额
            List<Map<Long, Double>> cidAmountMapList = new ArrayList<>();
            Set<Long> contractIdIn = new HashSet<>();
            for (Object object : bucketsArray) {
                if (object != null) {
                    JSONObject json = (JSONObject) object;
                    //第二层的结果
                    JSONArray jsonArrayTwo = json.getJSONObject("aggs02").getJSONArray("buckets");
                    //合同id--该合同对应的金额
                    Map<Long, Double> cidAmountMap = new HashMap<>(jsonArrayTwo.size());
                    for (Object objectTwo : jsonArrayTwo) {
                        JSONObject jsonObjectTwo = (JSONObject) objectTwo;
                        //桶内各个合同id
                        Long contractId = jsonObjectTwo.getLong("key");
                        //该合同对应的金额
                        Double value = jsonObjectTwo.getJSONObject("aggs03").getDoubleValue("value");

                        cidAmountMap.put(contractId, value);
                        contractIdIn.add(contractId);
                    }
                    cidAmountMapList.add(cidAmountMap);
                }
            }
            //-------------------处理币种、封装结果
            ElasticAggsPojo elasticAggsPojo = new ElasticAggsPojo();
            List<AggsBucketsPojo> aggsBucketsPojoList = elasticAggsPojo.getBuckets();
            double total = 0;
            List<ContractInfoVO> contractInfoVOList = formHelp.getContractListByIds(corpid, contractIdIn);
            //合同id--币种汇率对应关系map
            Map<Long, Double> cidRateMap = getExchangeRateMapFromContract(contractInfoVOList, currencyRateMap);
            for (Map<Long, Double> cidAmountMap : cidAmountMapList) {
                //每个桶内（各个合同）内总金额（转为人民币）
                double perAmount = 0;
                for (Entry<Long, Double> entry : cidAmountMap.entrySet()) {
                    double amount = getAmount4Payment(cidRateMap, entry);
                    perAmount += amount;
                    total += amount;
                }
                //保留2位小数
                perAmount = Arith.div(perAmount, 1, 2);
                //内部塞第一层的key（时间桶），value
                AggsBucketsPojo aggsBucketsPojo = new AggsBucketsPojo(null, String.valueOf(perAmount));
                aggsBucketsPojoList.add(aggsBucketsPojo);
            }
            //保留2位小数
            total = Arith.div(total, 1, 2);
            elasticAggsPojo.setTotal(String.valueOf(total));
            elasticAggsPojo.setBuckets(aggsBucketsPojoList);
            return elasticAggsPojo;
        }
    }

    /**
     * 如果返回单个数值，则只把时间范围内的进行排查聚合
     * 如果返回数组，则双层聚合：第一层按时间分桶，第二层要排重（如根据客户id排重等）
     * @param chartResultPojo  请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param valueTypeEnum    值类型
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param fieldTime        时间聚合字段（分桶）
     * @param fieldGroup       排重字段
     * @return com.xbongbong.paas.elasticsearch.pojo.vo.ElasticAggsPojo
     * @author zcp
     * @date 2019/2/21 13:46
     * indexTypeEnum 核对完成 已更改为indexModel中方法 @date 2022/5/6 15:35
     * @since v1.0
     * @version v1.0
     */
    public ElasticAggsPojo distinctAggregations(ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum, IndexTypeEnum indexTypeEnum,
                                                BoolQueryBuilder boolQueryBuilder, String fieldTime, String fieldGroup) throws XbbException {
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(valueTypeEnum, chartEntity, timeType);
        boolean flag = checkReturnFlag(returnTypeEnum);
        ElasticAggsPojo elasticAggsPojo = new ElasticAggsPojo();
        if (flag) {
            /*
              自定义时间的，封装成一个桶（即只获取到开始到结束时间内的所有数据）
              只按照fieldGroup字段进行排重
             */
            ValuesSourceAggregationBuilder valuesSourceAggregationBuilder = AggregationBuilders.cardinality("aggs01").field(fieldGroup);
            JSONObject retJson = esHelper.elasticAggs4NoBuckets2Object(indexTypeEnum, boolQueryBuilder, valuesSourceAggregationBuilder);
            JSONObject aggsJson = retJson.getJSONObject("aggregations");
            Long total = aggsJson.getJSONObject("aggs01").getLong("value");

            elasticAggsPojo.setTotal(String.valueOf(total));
            //当非数值型，请求自定义时间类型时，把结果处理成一个大桶
            elasticAggsPojo = formatResult4Custom(returnTypeEnum, elasticAggsPojo);
        } else {
            //双层聚合：第一层按时间分桶，第二层要排重（如根据客户id排重等）
            ValuesSourceAggregationBuilder valuesSourceAggregationBuilder = esHelper.getDateHistogramAggregation(chartResultPojo.getStartTime(), chartResultPojo.getEndTime(), fieldTime, timeType);
            CardinalityAggregationBuilder cardinalityAggregationBuilder = AggregationBuilders.cardinality("aggs02").field(fieldGroup);
            valuesSourceAggregationBuilder.subAggregation(cardinalityAggregationBuilder);

            /*--------得到聚合结果--------*/
            elasticAggsPojo = esHelper.elasticAggsSub(indexTypeEnum, boolQueryBuilder, valuesSourceAggregationBuilder);
        }
        return elasticAggsPojo;
    }

    /**
     * 封装只有一条线的一张图，一个chartId画出一个图（图上只有一条线）
     * @param elasticAggsPojo     单个统计聚合结果
     * @param chartResultPojo     请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @param throughDTO          穿透所需要的参数
     * @param tableFlag           是否画入表格内 0否1是
     * @param legend              echarts的legend
     * @param unit                单位
     * @param totalFlag           true代表有总数，且总数值为value；false则无右侧总数
     * @param valueTitle          总数标题，如“新增客户总数”
     * @return com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO
     * @throws
     * @author zcp
     * @date 2019/4/23 10:53
     * @since v1.0
     * @version v1.0
     */
    public ChartResultDetailVO packageResult4One(ElasticAggsPojo elasticAggsPojo, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO, ThroughDTO throughDTO, Integer tableFlag, String legend, String unit, Boolean totalFlag, String valueTitle) throws XbbException {
        //图上一条线，则默认y轴左侧，且不隐藏
        //分别采用两个不同单位的y轴，默认为yAxisIndex: 0，当为1时，代表另外一个y坐标轴
        Integer yAxisIndex = 0;
        //1隐藏，0不隐藏
        Integer hide = 0;
        //单条线信息
        StatisticResultInfoDTO statisticResultInfoDto = new StatisticResultInfoDTO(throughDTO, tableFlag, yAxisIndex, hide, legend, unit, elasticAggsPojo);
        List<StatisticResultInfoDTO> statisticResultInfoDtoList = Collections.singletonList(statisticResultInfoDto);
        TimePojo timePojo = throughHelp.formatThroughTimeInfo(false, chartResultPojo.getFiscalYearTimePojo(), chartResultPojo.getStartTime(), chartResultPojo.getEndTime());
        String through = throughHelp.getThrough(throughDTO, timePojo);
        //线信息塞入StatisticResultDTO
        StatisticResultDTO statisticResultDto = new StatisticResultDTO(totalFlag, elasticAggsPojo.getTotal(), through, valueTitle, statisticResultInfoDtoList);
        return packageResult(statisticResultDto, chartResultPojo, chartResultDetailVO);
    }

    /**
     * 封装阶段变更分析的结果集
     *
     * @param saleStageList       阶段list
     * @param list                统计聚合结果
     * @param chartResultPojo     请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @return com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO
     * @author zcp
     * @version v1.0
     * @since v1.0
     */
    public ChartResultDetailVO packageResult4StageTable(List<? extends PaasStageEntity> saleStageList, List<Map<String, StageFunnelInfoPojo>> list, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) {
        List<List<ResultDataTableVO>> tableData = new ArrayList<>();
        //当某个阶段某个时间无数据时，塞入空
        ResultDataTableVO defaultInfo = new ResultDataTableVO(BasicConstant.ZERO_STRING, null);
        //按行封装表格数据：阶段名+ 每个时间段内的数量、金额
        for (PaasStageEntity stageEntity : saleStageList) {
            String code = stageEntity.getCode();
            String name = stageEntity.getName();
            List<ResultDataTableVO> tableItem = new ArrayList<>();
            //表标题
            ResultDataTableVO resultDataTableTitle = new ResultDataTableVO(name, null);
            tableItem.add(resultDataTableTitle);
            //循环时间维度的list（先以时间聚合）
            for (Map<String, StageFunnelInfoPojo> codeInfoPojoMap : list) {
                /*
                  codeInfoPojoMap的格式
                  key：分组字段，阶段code
                  value：该分组内的总数量、及sum总值(double类型)
                 */
                StageFunnelInfoPojo stageFunnelInfoPojo = codeInfoPojoMap.get(code);
                if (Objects.isNull(stageFunnelInfoPojo)) {
                    //如果该阶段无数据，则塞入两个空的（数量、金额）
                    tableItem.add(defaultInfo);
                    tableItem.add(defaultInfo);
                    continue;
                }
                tableItem.add(stageFunnelInfoPojo.getCountDataTableVo());
                tableItem.add(stageFunnelInfoPojo.getValueDataTableVo());
            }
            tableData.add(tableItem);
        }

        ResultTableVO resultTableVo = new ResultTableVO();
        /*--------封装表resultTtableVO的信息--------*/
        //表头：["销售阶段","2019-1","2019-2","2019-3","2019-4","2019-5","2019-6","2019-7","2019-8","2019-9","2019-10","2019-11","2019-12"]
        List<String> tableTitleList = new ArrayList<>(chartResultPojo.getDateTitleList());
        List<TitleRightPojo> titleRightPojos = new ArrayList<>();
        //第一格销售阶段合并两行，居左
        titleRightPojos.add(new TitleRightPojo(BasicConstant.ONE, BasicConstant.TWO, null, I18nMessageUtil.getMessage(I18nStringConstant.OPPORTUNITY_STAGE), BasicConstant.ZERO));
        //时间标题的子标题居右
        List<TitleRightPojo> children = Arrays.asList(new TitleRightPojo(I18nMessageUtil.getMessage(I18nStringConstant.TITLE_COUNT), BasicConstant.ONE), new TitleRightPojo(I18nMessageUtil.getMessage(I18nStringConstant.TITLE_AMOUNT), BasicConstant.ONE));
        //时间标题合并两列、居中
        for (String title : tableTitleList) {
            titleRightPojos.add(new TitleRightPojo(BasicConstant.TWO, BasicConstant.ONE, children, title, BasicConstant.TWO));
        }
        resultTableVo.setTitle(titleRightPojos);
        resultTableVo.setData(tableData);
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        if (chartEntity != null) {
            //如果图信息存在，则把图相关信息置入chartResultChartVO
            BeanUtil.copyProperties(chartEntity, resultTableVo);
            //设置表的名称,导出用
            resultTableVo.setTableName(chartEntity.getName());
        } else {
            //在没有chart实体的时候,设置表的名称
            resultTableVo.setTableName(chartResultPojo.getSystemCodeEnum().getName());
        }
        //表相关塞入最终结果chartResultDetailVO内
        chartResultDetailVO.setTable(resultTableVo);
        return chartResultDetailVO;
    }

    /**
     * 封装一张图，一个chartId画出一个图（同时也支持图上面可能有多条线）
     * @param statisticResultDto  统计结果多条线的数据参数dto
     * @param chartResultPojo     请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @return com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO
     * @throws XbbException
     * @author zcp
     * @date 2019/4/23 9:43
     * @since v1.0
     * @version v1.0
     */
    public ChartResultDetailVO packageResult(StatisticResultDTO statisticResultDto, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        //图右侧总数，不画到echarts图上
        String value = ChartResultUtil.formatValue2String(false, chartResultPojo.getValueTypeEnum(), false, statisticResultDto.getValue(), NumFormatEnum.getByCode(chartResultPojo.getNumFormat()), chartResultPojo.getNumFormatFlag());
        statisticResultDto.setValue(value);
        //多个统计图线
        List<StatisticResultInfoDTO> statisticResultInfoDtoList = statisticResultDto.getStatisticResultInfoDtoList();
        /*--------声明图和表的信息VO--------*/
        //把总图的totalFlag、valueTitle、value置入图ResultChartVO
        ResultChartVO resultChartVo = BeanUtil.copyProperties(statisticResultDto, ResultChartVO.class, false);
        List<String> dateTitleList = new ArrayList<>(chartResultPojo.getDateTitleList());
        resultChartVo.setXAxis(dateTitleList);
        List<ResultDataChartVO> resultDataChartVoList = new ArrayList<>();
        ResultTableVO resultTableVo = new ResultTableVO();
        List<List<ResultDataTableVO>> resultDataTableVoList = new ArrayList<>();
        /*--------封装一个图resultChartVO的信息--------*/
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        String unit = null;
        if (chartEntity != null) {
            //如果图信息存在，则把图相关信息置入chartResultChartVO
            BeanUtil.copyProperties(chartEntity, resultChartVo);
            unit = chartEntity.getUnit();
            //设置表的名称,导出用
            resultTableVo.setTableName(chartEntity.getName());
        } else {
            //在没有chart实体的时候,设置表的名称
            resultTableVo.setTableName(chartResultPojo.getSystemCodeEnum().getName());
        }
        List<String> legendList = new ArrayList<>();
        /*
         处理图的data（即resultDataChartVOList），格式为：[{"name":"","value":[]}]
         处理表（即resultDataTableVOList）的信息
        */
        Integer now = DateUtil.getInt();
        for (StatisticResultInfoDTO sResultInfoDto : statisticResultInfoDtoList) {
            ThroughDTO throughDto = sResultInfoDto.getThroughDTO();
            ElasticAggsPojo elasticAggsPojo = sResultInfoDto.getElasticAggsPojo();
            Integer tableFlag = sResultInfoDto.getTableFlag();
            Integer yAxisIndex = sResultInfoDto.getYAxisIndex();
            yAxisIndex = (yAxisIndex == null) ? Integer.valueOf(0) : yAxisIndex;
            Integer hide = sResultInfoDto.getHide();
            Integer chartType = sResultInfoDto.getChartType();
            String legend = sResultInfoDto.getLegend();
            String perUnit = sResultInfoDto.getUnit();
            if (perUnit != null) {
                unit = perUnit;
            }
            // 数值格式化
            JSONObject dataFormObj = new JSONObject();
            dataFormObj.put(StringConstant.UNIT, unit);
            legendList.add(legend);
            JSONArray valueArray = new JSONArray();
            //每一个legend对应一行表格数据（即对应一个resultDataTableVOs）
            //处理各个桶内数据 单个数据
            List<ResultDataTableVO> resultDataTableVOs = formatValue(throughDto, elasticAggsPojo, chartResultPojo, now, valueArray);
            /*----图数据整体塞入resultDataChartVoList----*/
            ResultDataChartVO resultDataChartVO = new ResultDataChartVO(legend, valueArray, unit, yAxisIndex, hide, chartType, dataFormObj);
            resultDataChartVoList.add(resultDataChartVO);
            /*----表数据塞入resultDataTableVoList----*/
            if (BasicConstant.ZERO.equals(tableFlag)) {
                //该条线不画入表格
                continue;
            }
            //表标题
            String title = StringUtil.isEmpty(unit) ? legend : legend + "(" + unit + ")";
            ResultDataTableVO resultDataTableTitle = new ResultDataTableVO(title, null);
            resultDataTableVOs.add(0, resultDataTableTitle);
            resultDataTableVoList.add(resultDataTableVOs);
        }
        resultChartVo.setLegend(legendList);
        resultChartVo.setData(resultDataChartVoList);
        //图相关塞入最终结果chartResultDetailVO内
        List<ResultChartVO> resultChartVOList = Collections.singletonList(resultChartVo);
        chartResultDetailVO.setChart(resultChartVOList);
        /*--------封装表resultTtableVO的信息--------*/
        //表头：["日期","2019-1","2019-2","2019-3","2019-4","2019-5","2019-6","2019-7","2019-8","2019-9","2019-10","2019-11","2019-12"]
        List<String> tableTitleList = new ArrayList<>(chartResultPojo.getDateTitleList());
        tableTitleList.add(0, I18nMessageUtil.getMessage(CommonConstant.DATA));
        resultTableVo.setTitle(titleNumRight(tableTitleList));
        resultTableVo.setData(resultDataTableVoList);
        //表相关塞入最终结果chartResultDetailVO内
        chartResultDetailVO.setTable(resultTableVo);
        return chartResultDetailVO;
    }

    /**
     * 封装单个图（或总数），无表
     * @param chartResultPojo     请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @param resultChartVo       图的信息VO
     * @param valueArray          结果数组
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultDetailVO
     * @author zcp
     * @date 2019/2/23 13:54
     * @since v1.0
     * @version v1.0
     */
    public ChartResultDetailVO packageResult4Chart(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO, ResultChartVO resultChartVo, JSONArray valueArray) {
        List<ResultDataChartVO> resultDataChartVOList = new ArrayList<>();
        /*--------封装一个图resultChartVO的信息--------*/
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        String name = "";
        String unit = "";
        if (chartEntity != null) {
            //如果图信息存在，则把图相关信息置入chartResultChartVO
            BeanUtil.copyProperties(chartEntity, resultChartVo);
            List<String> dateTitleList = new ArrayList<>(chartResultPojo.getDateTitleList());
            resultChartVo.setXAxis(dateTitleList);
            name = chartEntity.getName();
            unit = chartEntity.getUnit();
        }
        /*----图数据整体塞入resultDataChartVOList----*/
        ResultDataChartVO resultDataChartVO = new ResultDataChartVO(name, unit, valueArray);
        resultDataChartVOList.add(resultDataChartVO);
        resultChartVo.setData(resultDataChartVOList);
        //图相关塞入最终结果chartResultDetailVO内
        List<ResultChartVO> resultChartVOList = new ArrayList<>();
        resultChartVOList.add(resultChartVo);
        chartResultDetailVO.setChart(resultChartVOList);
        return chartResultDetailVO;
    }

    /**
     * 封装一张表的table数据
     * @param elasticAggsPojo 单个统计聚合结果
     * @param chartResultPojo 请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param throughDTO      穿透所需要的参数
     * @param legend          echarts的legend
     * @param unit            单位
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultDetailVO
     * @author youli.chen
     * @date 2019/2/19 19:21
     * @since v1.0
     * @version v1.0
     * @update 2019-4-23 优化方法 by zcp
     */
    public List<List<ResultDataTableVO>> packageResult4TableOne(ElasticAggsPojo elasticAggsPojo, ChartResultPojo chartResultPojo, ThroughDTO throughDTO, String legend, String unit) throws XbbException {
        StatisticResultInfoDTO activeStatisticResultInfoDto = new StatisticResultInfoDTO(throughDTO, 1, legend, unit, elasticAggsPojo);
        List<StatisticResultInfoDTO> statisticResultInfoDtoList = Collections.singletonList(activeStatisticResultInfoDto);
        StatisticResultDTO statisticResultDto = new StatisticResultDTO(false, "", null, statisticResultInfoDtoList);
        return packageResult4TableMore(statisticResultDto, chartResultPojo);
    }

    /**
     * 封装一张表的table数据，有多行数据
     * @param statisticResultDTO 表数据dto
     * @param chartResultPojo    请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultDetailVO
     * @author youli.chen
     * @date 2019/2/19 19:01
     * @since v1.0
     * @version v1.0
     * @update 2019-4-23 优化方法 by zcp
     */
    public List<List<ResultDataTableVO>> packageResult4TableMore(StatisticResultDTO statisticResultDTO, ChartResultPojo chartResultPojo) throws XbbException {
        List<StatisticResultInfoDTO> statisticResultInfoDtoList = statisticResultDTO.getStatisticResultInfoDtoList();
        //声明表的信息VO
        List<List<ResultDataTableVO>> resultDataTableVoList = new ArrayList<>();
        /*--------封装一个图resultChartVO的信息--------*/
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        String unit = "";
        if (chartEntity != null) {
            unit = chartEntity.getUnit();
        }
        /*
         处理图的data（即resultDataChartVOList），格式为：[{"name":"","value":[]}]
         处理表（即resultDataTableVOList）的信息
        */
        Integer now = DateUtil.getInt();
        for (StatisticResultInfoDTO sResultInfoDto : statisticResultInfoDtoList) {
            ThroughDTO throughDTO = sResultInfoDto.getThroughDTO();
            ElasticAggsPojo elasticAggsPojo = sResultInfoDto.getElasticAggsPojo();
            String legend = sResultInfoDto.getLegend();
            String perUnit = sResultInfoDto.getUnit();
            if (perUnit != null) {
                unit = perUnit;
            }
            JSONArray valueArray = new JSONArray();
            //处理各个桶内数据
            //每一个legend对应一行表格数据（即对应一个resultDataTableVOs）
            List<ResultDataTableVO> resultDataTableVOs = formatValue(throughDTO, elasticAggsPojo, chartResultPojo, now, valueArray);
            //表标题
            String title = StringUtil.isEmpty(unit) ? legend : legend + "(" + unit + ")";
            ResultDataTableVO resultDataTableTitle = new ResultDataTableVO(title, null);
            resultDataTableVOs.add(0, resultDataTableTitle);

            /*----表数据塞入resultDataTableVOList----*/
            resultDataTableVoList.add(resultDataTableVOs);
        }
        return resultDataTableVoList;
    }

    /**
     * 封装饼图类型结果，如客户画像（行业、来源、状态、地图）、跟进方式、输单原因等
     * @param tableName           表格第一行第一列名称：客户行业、来源、状态
     * @param pieInfoDtoList      图表中心饼图类型dto列表，客户画像是一次画两张图，所以传list（chart表内一条数据，但是要一次性画出两张图）
     * @param chartResultPojo     请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultDetailVO
     * @author zcp
     * @date 2019/2/24 21:19
     * @since v1.0
     * @version v1.0
     */
    public ChartResultDetailVO packageResult4Pie(String tableName, List<StatisticResultPieInfoDTO> pieInfoDtoList, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) {
        //声明图和表的信息VO
        List<ResultChartVO> resultChartVoList = new ArrayList<>();
        ResultTableVO resultTableVo = new ResultTableVO();
        List<List<ResultDataTableVO>> resultDataTableVoList = new ArrayList<>();

        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        List<String> tableTitleList = new ArrayList<>();
        for (StatisticResultPieInfoDTO pieInfoDto : pieInfoDtoList) {
            String chartName = pieInfoDto.getName();
            List<ResultDataSeriesChartVO> dataSeriesList = pieInfoDto.getDataSeriesList();
            ThroughDTO throughDTO = pieInfoDto.getThroughDTO();
            /*--------封装一个图resultChartVO的信息--------*/
            ResultChartVO resultChartVo = new ResultChartVO();
            if (chartEntity != null) {
                //如果图信息存在，则把图相关信息置入chartResultChartVO
                BeanUtil.copyProperties(chartEntity, resultChartVo);
                //设置表的名称,导出用
                resultTableVo.setTableName(chartEntity.getName());
            } else {
                //在没有chart实体的时候,设置表的名称
                resultTableVo.setTableName(chartResultPojo.getSystemCodeEnum().getName());
            }
            //名字不是图表的名称，要置为传入的name，此处为“所有客户”、“成交客户”
            resultChartVo.setName(chartName);
            //置入图的dataSeries，不用设置data，此种情形用不到data
            resultChartVo.setDataSeries(dataSeriesList);
            List<String> legend = new ArrayList<>();
            //表格标题要用数据字典的真实名称
            List<String> realNameList = new ArrayList<>();
            /*--------封装一个图resultChartVO的信息--------*/
            //每一个legend对应一行表格数据（即对应一个resultDataTableVOs）
            List<ResultDataTableVO> resultDataTableVOs = formatData4PieTable(throughDTO, chartResultPojo, dataSeriesList, legend, realNameList);
            resultChartVo.setLegend(legend);
            //把“所有客户”，“成交客户”置入第一列
            chartName +=  getUnit("(" + I18nMessageUtil.getMessage(CommonConstant.EACH) + ")");
            ResultDataTableVO columnResultDataTableVO = new ResultDataTableVO(chartName, null);
            resultDataTableVOs.add(0, columnResultDataTableVO);

            /*----每张图塞入resultChartVOList----*/
            resultChartVoList.add(resultChartVo);
            //可能所有客户有“未知”项，或者成交客户有“未知”项
            if (realNameList.size() > tableTitleList.size()) {
                tableTitleList = realNameList;
            }
            /*----表数据塞入resultDataTableVOList----*/
            resultDataTableVoList.add(resultDataTableVOs);
        }
        //图相关塞入最终结果chartResultDetailVO内
        chartResultDetailVO.setChart(resultChartVoList);
        /*--------只有当不是处理地图时，才会封装表resultTtableVO的信息--------*/
        //把title置入最前
        tableTitleList.add(0, tableName);
        resultTableVo.setTitle(titleNumRight(tableTitleList));
        resultTableVo.setData(resultDataTableVoList);
        //表相关塞入最终结果chartResultDetailVO内
        chartResultDetailVO.setTable(resultTableVo);
        return chartResultDetailVO;
    }

    /**
     * 封装无表格的饼图，也无穿透。
     * 如，产品分类销量占比图结果
     *
     * @param statisticResultPieInfoDto 单个饼图dto
     * @param chartResultPojo           请求系统指标接口需要用到的pojo类
     * @param chartResultDetailVO       统计需要渲染的图
     * @return com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO
     * @author 徐俊杰
     * @since v1.0
     * @date 2019/4/8 20:03
     * @update 2019-4-23 从CrmProductResult移入，并优化该方法
     */
    public ChartResultDetailVO packageResult4PieNoTable(StatisticResultPieInfoDTO statisticResultPieInfoDto, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) {
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        /*--------封装一个图resultChartVO的信息--------*/
        ResultChartVO resultChartVo = new ResultChartVO();
        if (chartEntity != null) {
            //如果图信息存在，则把图相关信息置入chartResultChartVO
            BeanUtil.copyProperties(chartEntity, resultChartVo);
        }
        //饼图名称
        resultChartVo.setName(statisticResultPieInfoDto.getName());
        //每个图的dataSeries
        List<ResultDataSeriesChartVO> dataSeries = statisticResultPieInfoDto.getDataSeriesList();
        //置入图的dataSeries，不用设置data，此种情形用不到data
        resultChartVo.setDataSeries(dataSeries);
        //系列
        List<String> legend = new ArrayList<>();
        for (ResultDataSeriesChartVO resultDataSeriesChartVo : dataSeries) {
            legend.add(resultDataSeriesChartVo.getName());
        }
        resultChartVo.setLegend(legend);
        /*----每张图塞入resultChartVOList----*/
        //声明图和表的信息VO
        List<ResultChartVO> resultChartVoList = Collections.singletonList(resultChartVo);
        //图相关塞入最终结果chartResultDetailVO内
        chartResultDetailVO.setChart(resultChartVoList);
        return chartResultDetailVO;
    }

    /**
     * 封装地图
     * @param mapLegend           地图legend
     * @param pieInfoDtoList      图表中心地图类型dto列表，客户画像是一次画两张图，所以传list（chart表内一条数据，但是要一次性画出两张图）
     * @param chartResultPojo     请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @return com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO
     * @author zcp
     * @date 2019/4/22 16:14
     * @since v1.0
     * @version v1.0
     */
    public ChartResultDetailVO packageResult4Map(String mapLegend, List<StatisticResultPieInfoDTO> pieInfoDtoList, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) {
        //声明图和表的信息VO
        List<ResultChartVO> resultChartVoList = new ArrayList<>();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        List<String> legend = Collections.singletonList(mapLegend);
        for (StatisticResultPieInfoDTO pieInfoDto : pieInfoDtoList) {
            /*--------封装一个图resultChartVO的信息--------*/
            ResultChartVO resultChartVo = new ResultChartVO();
            if (chartEntity != null) {
                //如果图信息存在，则把图相关信息置入chartResultChartVO
                BeanUtil.copyProperties(chartEntity, resultChartVo);
            }
            //名字不是图表的名称，要置为传入的name，此处为“所有客户”、“成交客户”
            resultChartVo.setName(pieInfoDto.getName());
            //置入图的dataSeries，不用设置data，此种情形用不到data
            resultChartVo.setDataSeries(pieInfoDto.getDataSeriesList());
            resultChartVo.setLegend(legend);
            /*----每张图塞入resultChartVOList----*/
            resultChartVoList.add(resultChartVo);
        }
        //图相关塞入最终结果chartResultDetailVO内
        chartResultDetailVO.setChart(resultChartVoList);
        return chartResultDetailVO;
    }

    /**
     * （堆叠条形图）完成工单平均耗时分析的图、表数据包装
     *
     * @param chartResultPojo     入参
     * @param chartResultDetailVO 回参
     * @param data4Actual         按时
     * @param data4Expected       预计
     * @param data4ActualOver     超时
     * @param expectedList        表格、导出用：预计耗时list
     * @param actualList          表格、导出用：实际耗时list
     * @param xAxis               阶段
     * @return com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO
     * @author 徐俊杰
     * @date 2019/4/19 9:38
     * @since v1.0
     */
    public ChartResultDetailVO packageResult4StackedBar(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO,
                                                        JSONArray data4Actual, JSONArray data4Expected,
                                                        JSONArray data4ActualOver, List<Double> expectedList,
                                                        List<Double> actualList, List<String> xAxis) {
        //图的列表
        List<ResultChartVO> resultChartVOList = new ArrayList<>();
        //图
        ResultChartVO resultChartVO = new ResultChartVO();
        //表
        ResultTableVO table = new ResultTableVO();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        String unit = null;
        if (chartEntity != null) {
            //图的基础信息
            BeanUtil.copyProperties(chartEntity, resultChartVO);
            unit = chartEntity.getUnit();
            //设置表的名称,导出用
            table.setTableName(chartEntity.getName());
        }
        //图数据
        List<ResultDataChartVO> data = new ArrayList<>();
        //图的系列
        List<String> legend = new ArrayList<>();
        legend.add(I18nMessageUtil.getMessage(I18nStringConstant.ACTUAL_TIME_ON_TIME));
        ResultDataChartVO actualInTime4Chart = new ResultDataChartVO(I18nMessageUtil.getMessage(I18nStringConstant.ACTUAL_TIME_ON_TIME), unit, data4Actual);
        data.add(actualInTime4Chart);
        legend.add(I18nMessageUtil.getMessage(I18nStringConstant.EXPECTED_TIME));
        ResultDataChartVO expectedTime4Chart = new ResultDataChartVO(I18nMessageUtil.getMessage(I18nStringConstant.EXPECTED_TIME), unit, data4Expected);
        data.add(expectedTime4Chart);
        legend.add(I18nMessageUtil.getMessage(I18nStringConstant.ACTUAL_TIME_TIME_OUT));
        ResultDataChartVO actualOverTime4Chart = new ResultDataChartVO(I18nMessageUtil.getMessage(I18nStringConstant.ACTUAL_TIME_TIME_OUT), unit, data4ActualOver);
        data.add(actualOverTime4Chart);
        resultChartVO.setXAxis(xAxis);
        resultChartVO.setLegend(legend);
        resultChartVO.setData(data);
        resultChartVOList.add(resultChartVO);
        chartResultDetailVO.setChart(resultChartVOList);
        //表格标题
        List<String> title = new ArrayList<>();
        //表格标题
        title.add(I18nMessageUtil.getMessage(CommonConstant.STAGE));
        title.addAll(xAxis);
        table.setTitle(titleNumRight(title));
        //表格数据
        List<List<ResultDataTableVO>> tableData = new ArrayList<>();
        //处理预计耗时
        Integer stageSize = xAxis.size();
        List<ResultDataTableVO> expectedTime = workOrderTableData(expectedList, stageSize, I18nMessageUtil.getMessage(I18nStringConstant.EXPECTED_TIME_HOUR));
        tableData.add(expectedTime);
        //处理实际耗时
        List<ResultDataTableVO> actualTime = workOrderTableData(actualList, stageSize, I18nMessageUtil.getMessage(I18nStringConstant.ACTUAL_TIME_HOUR));
        tableData.add(actualTime);
        table.setData(tableData);
        chartResultDetailVO.setTable(table);
        return chartResultDetailVO;
    }

    /**
     * 两层聚合(计算首次成交、再次成交相关)
     * @param chartResultPojo        参数pojo
     * @param indexTypeEnum          索引，用于获取index和type
     * @param boolQueryBuilder       查询条件
     * @param agoToStartCustomerIdIn 存放历史成交的客户ids（已经与某个模版的客户ids取过交集）
     * @param valueTypeEnum          数值类型
     * @return com.xbongbong.pro.statistic.pojo.CustomerOkFirstSecondPojo
     * @author zcp
     * @date 2019/2/22 21:52
     * @since v1.0
     * @version v1.0
     */
    public CustomerOkFirstSecondPojo firstAndSecondAggregations(ChartResultPojo chartResultPojo, IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, Set<Long> agoToStartCustomerIdIn, ValueTypeEnum valueTypeEnum) throws XbbException {
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        CustomerOkFirstSecondPojo customerOkFirstSecondPojo = new CustomerOkFirstSecondPojo();
        //首次成交list
        List<Integer> firstResultList = customerOkFirstSecondPojo.getFirstResultList();
        //再次成交list
        List<Integer> secondResultList = customerOkFirstSecondPojo.getSecondResultList();
        //各个桶内，首次成交客户id list
        List<Set<Long>> firstCustomerIds = customerOkFirstSecondPojo.getFirstCustomerIds();
        //各个桶内，再次成交客户id list
        List<Set<Long>> secondCustomerIds = customerOkFirstSecondPojo.getSecondCustomerIds();
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(valueTypeEnum, chartEntity, timeType);
        boolean flag = checkReturnFlag(returnTypeEnum);
        String fieldGroup = ContractEnum.getEsAttr4Keyword(ContractEnum.LINK_CUSTOMER);
        if (flag) {
            /*
              自定义时间的，封装成一个桶（即只获取到开始到结束时间内的所有数据）
              只按照关联客户字段进行terms聚合
             */
            JSONArray bucketsArray = getTermsBuckets(indexTypeEnum, boolQueryBuilder, fieldGroup);
            customerOkFirstSecondPojo = formatFirstAndSecondPojo(bucketsArray, agoToStartCustomerIdIn, customerOkFirstSecondPojo, firstResultList, secondResultList, firstCustomerIds, secondCustomerIds);
        } else {
            String fieldTime = ContractEnum.getEsAttr4Keyword(ContractEnum.SIGN_TIME);
            JSONArray bucketsArray = getDateAndTermsBuckets(chartResultPojo, indexTypeEnum, boolQueryBuilder, timeType, fieldTime, fieldGroup);
            for (Object object : bucketsArray) {
                if (object != null) {
                    JSONObject json = (JSONObject) object;
                    Integer outCount = json.getInteger("doc_count");
                    //空桶
                    if (outCount == 0) {
                        firstResultList.add(0);
                        secondResultList.add(0);
                        firstCustomerIds.add(new HashSet<>());
                        secondCustomerIds.add(new HashSet<>());
                    } else {
                        JSONArray innerJsonArray = json.getJSONObject("aggs02").getJSONArray("buckets");
                        if (innerJsonArray != null && !innerJsonArray.isEmpty()) {
                            customerOkFirstSecondPojo = formatFirstAndSecondPojo(innerJsonArray, agoToStartCustomerIdIn, customerOkFirstSecondPojo, firstResultList, secondResultList, firstCustomerIds, secondCustomerIds);
                        } else {
                            firstResultList.add(0);
                            secondResultList.add(0);
                            firstCustomerIds.add(new HashSet<>());
                            secondCustomerIds.add(new HashSet<>());
                        }
                    }
                }
            }
        }
        customerOkFirstSecondPojo.setFirstResultList(firstResultList);
        customerOkFirstSecondPojo.setSecondResultList(secondResultList);
        customerOkFirstSecondPojo.setFirstCustomerIds(firstCustomerIds);
        customerOkFirstSecondPojo.setSecondCustomerIds(secondCustomerIds);
        return customerOkFirstSecondPojo;
    }

    /**
     * 两层聚合(计算首次成交、再次成交相关)
     * @param chartResultPojo        参数pojo
     * @param boolQueryBuilder       查询条件
     * @param valueTypeEnum          数值类型
     * @return com.xbongbong.pro.statistic.pojo.CustomerOkFirstSecondPojo
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    public CustomerOkFirstSecondPojo firstAndSecondAggregations(ChartResultPojo chartResultPojo, BoolQueryBuilder boolQueryBuilder, ValueTypeEnum valueTypeEnum) throws XbbException {
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        CustomerOkFirstSecondPojo customerOkFirstSecondPojo = new CustomerOkFirstSecondPojo();
        //各个桶内，首次成交客户id list
        List<Set<Long>> firstCustomerIds = customerOkFirstSecondPojo.getFirstCustomerIds();
        //各个桶内，再次成交客户id list
        List<Set<Long>> secondCustomerIds = customerOkFirstSecondPojo.getSecondCustomerIds();
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(valueTypeEnum, chartEntity, timeType);
        boolean flag = checkReturnFlag(returnTypeEnum);
        String fieldGroup = ContractEnum.getEsAttr4Keyword(ContractEnum.LINK_CUSTOMER);
        //--------------------------（1）第一步：聚合合同的关联客户id：获取startTime 到 endTime时间区间内所有的成交客户（首次、复购）
        IndexTypeEnum indexTypeEnum = IndexTypeEnum.IDX_SAAS_CONTRACT;
        if (flag) {
            /*
              自定义时间的，封装成一个桶（即只获取到开始到结束时间内的所有数据）
              只按照关联客户字段进行terms聚合
             */
            JSONArray bucketsArray = getTermsBuckets(indexTypeEnum, boolQueryBuilder, fieldGroup);
            //首次、多次的分开塞到不同集合 firstCustomerIds、secondCustomerIds，然后把所有首次成交的汇总塞入firstCids（即只有时间区间内首次成交的客户需要再次查询验证startTime之前是否签过合同）
            customerOkFirstSecondPojo = formatFirstAndSecondPojoRefactor(bucketsArray, customerOkFirstSecondPojo, firstCustomerIds, secondCustomerIds);
        } else {
            String fieldTime = ContractEnum.getEsAttr4Keyword(ContractEnum.SIGN_TIME);
            JSONArray bucketsArray = getDateAndTermsBuckets(chartResultPojo, indexTypeEnum, boolQueryBuilder, timeType, fieldTime, fieldGroup);
            for (Object object : bucketsArray) {
                if (object != null) {
                    JSONObject json = (JSONObject) object;
                    Integer outCount = json.getInteger("doc_count");
                    //空桶
                    if (outCount == 0) {
                        firstCustomerIds.add(new HashSet<>());
                        secondCustomerIds.add(new HashSet<>());
                    } else {
                        JSONArray innerJsonArray = json.getJSONObject("aggs02").getJSONArray("buckets");
                        if (innerJsonArray != null && !innerJsonArray.isEmpty()) {
                            customerOkFirstSecondPojo = formatFirstAndSecondPojoRefactor(innerJsonArray, customerOkFirstSecondPojo, firstCustomerIds, secondCustomerIds);
                        } else {
                            firstCustomerIds.add(new HashSet<>());
                            secondCustomerIds.add(new HashSet<>());
                        }
                    }
                }
            }
        }
        customerOkFirstSecondPojo.setFirstCustomerIds(firstCustomerIds);
        customerOkFirstSecondPojo.setSecondCustomerIds(secondCustomerIds);
        //--------------------------（2）第二步：查合同，findByScroll：用（1）的首次成交客户id为条件获取startTime之前所有的成交客户id
        /*
          ①通过该方法得到的是时间段内（start -- end）所有非首次的customerId集合（在各个时间桶内不一定就是复购，所以各个桶还要经过dealWithFirstSecond方法处理）
          ②设置总桶首次、再次成交数
         */
        Set<Long> agoToStartSecondCustomerIdIn = formHelp.getAgoToStartSecondCustomerIdIn(chartResultPojo, customerOkFirstSecondPojo);
        //--------------------------（3）第三步：内存计算：用（1）、（2）得到的结果摘出first内的复购客户id，塞入second
        //首次成交list
        List<Integer> firstResultList = customerOkFirstSecondPojo.getFirstResultList();
        //再次成交list
        List<Integer> secondResultList = customerOkFirstSecondPojo.getSecondResultList();
        for (int i = 0; i < firstCustomerIds.size(); i++) {
            Set<Long> firstIdSet = firstCustomerIds.get(i);
            Set<Long> secondIdSet = secondCustomerIds.get(i);
            if (CollectionsUtil.isNotEmpty(firstIdSet)) {
                dealWithFirstSecond(agoToStartSecondCustomerIdIn, firstIdSet, secondIdSet);
            }
            //首次成交
            Integer innerFirstTotal = firstIdSet.size();
            //再次成交
            Integer innerSecondTotal = secondIdSet.size();
            firstResultList.add(innerFirstTotal);
            secondResultList.add(innerSecondTotal);
        }
        customerOkFirstSecondPojo.setFirstResultList(firstResultList);
        customerOkFirstSecondPojo.setSecondResultList(secondResultList);
        return customerOkFirstSecondPojo;
    }

    /**
     * 获取第二层聚合的key合集 ,不直接返回
     * 主要封装排重结果（不能直接算total数，而是要把key塞入集合，从而得到所有排重后的key合集）
     * @param chartResultPojo  参数pojo
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param fieldTime        时间聚合字段（分桶）
     * @param fieldGroup       排重字段
     * @param totalFlag        true 返回总数， false 不返回
     * @return com.xbongbong.pro.statistic.pojo.FloorAggsKeyPojo
     * @author zcp
     * @date 2019/2/23 16:08
     * @lastTime indexTypeEnum 核对完成 已更改为indexModel中方法 @date 2022/5/6 15:35
     * @since v1.0
     * @version v1.0
     */
    public FloorAggsKeyPojo getFloorAggsKey(ChartResultPojo chartResultPojo, IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String fieldTime, String fieldGroup, boolean totalFlag) throws XbbException {
        FloorAggsKeyPojo floorAggsKeyPojo = new FloorAggsKeyPojo();
        Set totalSet = floorAggsKeyPojo.getTotalSet();
        /**
         LinkedHashMap
         key-日期
         value-id数组（比如客户id、机会id、员工id），其中set的泛型为Integer或String
         */
        Map<String, Set> resultMap = floorAggsKeyPojo.getResultMap();

        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(ValueTypeEnum.ARRAY_TYPE, chartEntity, timeType);
        boolean flag = checkReturnFlag(returnTypeEnum);
        if (flag) {
            /*
              自定义时间的，封装成一个桶（即只获取到开始到结束时间内的所有数据）
              只按照关联客户字段进行terms聚合
             */
            JSONArray bucketsArray = getTermsBuckets(indexTypeEnum, boolQueryBuilder, fieldGroup);
            floorAggsKeyPojo = formatFloorAggsKeyPojo(bucketsArray, floorAggsKeyPojo, "", resultMap, totalSet, totalFlag);
        } else {
            //双层聚合：第一层按签订时间分桶，第二层要排重（如根据客户id排重等）
            JSONArray bucketsArray = getDateAndTermsBuckets(chartResultPojo, indexTypeEnum, boolQueryBuilder, timeType, fieldTime, fieldGroup);
            for (Object object : bucketsArray) {
                if (object != null) {
                    JSONObject json = (JSONObject) object;
                    Integer outCount = json.getInteger("doc_count");
                    String mapKey = json.getString("key_as_string");
                    //空桶
                    if (outCount == 0) {
                        resultMap.put(mapKey, new HashSet<>());
                    } else {
                        JSONArray innerJsonArray = json.getJSONObject("aggs02").getJSONArray("buckets");
                        if (innerJsonArray != null && !innerJsonArray.isEmpty()) {
                            floorAggsKeyPojo = formatFloorAggsKeyPojo(innerJsonArray, floorAggsKeyPojo, mapKey, resultMap, totalSet, totalFlag);
                        } else {
                            resultMap.put(mapKey, new HashSet<>());
                        }
                    }
                }
            }
        }
        floorAggsKeyPojo.setTotalSet(totalSet);
        floorAggsKeyPojo.setResultMap(resultMap);
        return floorAggsKeyPojo;
    }

    /**
     * 关于数据字典的统计（value不存在的要补0）
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param fieldGroup       要统计的字段（用其聚合）
     * @param dictionaryList   数据字典list
     * @return java.util.List<com.xbongbong.pro.statistic.pojo.vo.ResultDataSeriesChartVO>
     * @author zcp
     * @date 2019/2/24 17:11
     * @lastTime indexTypeEnum 核对完成 已更改为indexModel中方法 @date 2022/5/6 15:35
     * @since v1.0
     * @version v1.0
     */
    public List<ResultDataSeriesChartVO> getDataDictionaryDataSeries(IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String fieldGroup, List<ItemPoJo> dictionaryList) throws XbbException {
        //terms聚合
        JSONArray bucketsArray = getTermsBuckets(indexTypeEnum, boolQueryBuilder, fieldGroup);
        Map<Object, Integer> dataMap = new HashMap<>(bucketsArray.size());
        for (Object object : bucketsArray) {
            if (object != null) {
                JSONObject dataJsonObject = (JSONObject) object;
                String key = dataJsonObject.getString("key");
                dataMap.put(key, dataJsonObject.getInteger("doc_count"));
            }
        }
        List<ResultDataSeriesChartVO> resultDataSeriesChartVOList = new ArrayList<>();
        for (ItemPoJo itemPojo : dictionaryList) {
            String code = itemPojo.getValue().toString();
            String realName = itemPojo.getText();
            Integer value = dataMap.get(code);
            //处理过该字典，则移除
            dataMap.remove(code);
            int valueInt = value == null ? 0 : value;
            String name = realName + "[" + valueInt + "]" + getUnit(I18nMessageUtil.getMessage(CommonConstant.EACH));
            ResultDataSeriesChartVO resultDataSeriesChartVO = new ResultDataSeriesChartVO();
            resultDataSeriesChartVO.setName(name);
            resultDataSeriesChartVO.setRealName(realName);
            resultDataSeriesChartVO.setCode(code);
            resultDataSeriesChartVO.setValue(String.valueOf(valueInt));
            resultDataSeriesChartVOList.add(resultDataSeriesChartVO);
        }
        if (dataMap.size() > 0) {
            //说明还有其他字典（一些未启用的，或未设置字典的数据）
            Integer other = 0;
            //考虑到数据字典可能关闭，位置应该是剩余的，而不应该是主动根据“”或0去获取
            for (Entry<Object, Integer> entry : dataMap.entrySet()) {
                if (entry.getValue() != null) {
                    other += entry.getValue();
                }
            }
            String realName = I18nMessageUtil.getMessage(I18nStringConstant.UNKNOW);
            String name = realName + "[" + other + "]" + getUnit(I18nMessageUtil.getMessage(CommonConstant.EACH));
            String value = String.valueOf(other);
            ResultDataSeriesChartVO otherVO = new ResultDataSeriesChartVO(name, realName, name, value);
            resultDataSeriesChartVOList.add(otherVO);
        }
        return resultDataSeriesChartVOList;
    }

    /**
     * 获取工单评价的DataSeries
     *
     * @param rateStarEntityExts 各个星级对应数量
     * @return java.util.List<com.xbongbong.pro.statistic.result.pojo.vo.ResultDataSeriesChartVO>
     * @throws XbbException 数据库数据异常
     * @author 徐俊杰
     * @date 2019/4/22 10:28
     * @since v1.0
     */
    public List<ResultDataSeriesChartVO> getDataSeries4WorkOrderDegree(List<WorkOrderEvaluateRateStarEntityExt> rateStarEntityExts) throws XbbException {
        List<ResultDataSeriesChartVO> dataSeriesList = new ArrayList<>();
        for (WorkOrderEvaluateRateStarEntityExt entityExt : rateStarEntityExts) {
            Integer code = entityExt.getRateStar();
            Integer value = entityExt.getNum();
            String realName = StarEnum.getTextByValue(code);
            String name = realName + "[" + value + "]";
            ResultDataSeriesChartVO dataSeries = new ResultDataSeriesChartVO();
            //set数据
            dataSeries.setCode(String.valueOf(code));
            dataSeries.setName(name);
            dataSeries.setRealName(realName);
            dataSeries.setValue(String.valueOf(value));
            dataSeriesList.add(dataSeries);
        }
        return dataSeriesList;
    }

    /**
     * 获取客户地图画像数据
     * @param chartResultPojo  参数pojo
     * @param boolQueryBuilder 查询条件
     * @return java.util.List<com.xbongbong.pro.statistic.pojo.vo.ResultDataSeriesChartVO>
     * @author zcp
     * @date 2019/2/25 9:10
     * @since v1.0
     * @version v1.0
     */
    public List<ResultDataSeriesChartVO> getCustomerDrawRegionMap(ChartResultPojo chartResultPojo, BoolQueryBuilder boolQueryBuilder) throws XbbException {
        String province = chartResultPojo.getProvince();
        boolean bool = StringUtil.isEmpty(province) || MapConstant.FULL.equals(province) || MapConstant.CHINA.equals(province);
        IndexTypeEnum indexTypeEnum =  indexTypeModel.getCustomerIndex(chartResultPojo.getCorpid(), XbbRefTypeEnum.CUSTOMER_MANAGEMENT.getCode(), SaasMarkEnum.SAAS.getCode());

        String provinceAttr = CustomerManagementEnum.getAddress4Aggs(CustomerManagementEnum.ADDRESS, "province");
        String districtAttr = CustomerManagementEnum.getAddress4Aggs(CustomerManagementEnum.ADDRESS, "district");
        String cityAttr = CustomerManagementEnum.getAddress4Aggs(CustomerManagementEnum.ADDRESS, "city");

        TermsAggregationBuilder termsAggregationBuilder;
        if (bool) {
            //筛选全国
            termsAggregationBuilder = AggregationBuilders.terms("aggs01").field(provinceAttr);
        } else {
            //筛选某个省
            if (MapConstant.PROVINCE_END_WITH_CITY.contains(province)) {
                //部分比较特殊，是以市结尾的
                boolQueryBuilder.filter(termQuery(provinceAttr, province + I18nMessageUtil.getMessage(CommonConstant.CITY)));
                termsAggregationBuilder = AggregationBuilders.terms("aggs01").field(districtAttr);
            } else if (province.endsWith(I18nMessageUtil.getMessage(CommonConstant.CITY))){
                boolQueryBuilder.filter(termQuery(provinceAttr, province));
                termsAggregationBuilder = AggregationBuilders.terms("aggs01").field(districtAttr);
            }else {
                /**
                 分两种：
                 （1）省级别的区域：结尾没有“省”，也没有“市”
                 （2）直接以“省”结尾
                 */
                if (MapConstant.PROVINCE_END_WITH_NOTHING.contains(province)) {
                    boolQueryBuilder.filter(termQuery(provinceAttr, province));
//                } else if (province.endsWith(I18nMessageUtil.getMessage(CommonConstant.AUTONOMOUS_REGION))){
//                    boolQueryBuilder.filter(termQuery(provinceAttr, province.replaceAll(I18nMessageUtil.getMessage(CommonConstant.AUTONOMOUS_REGION),"")));
//                } else if (province.endsWith(I18nMessageUtil.getMessage(CommonConstant.SPECIAL_ADMINISTRATIVE_REGION))){
//                    boolQueryBuilder.filter(termQuery(provinceAttr, province.replaceAll(I18nMessageUtil.getMessage(CommonConstant.SPECIAL_ADMINISTRATIVE_REGION),"")));
//                }else if (province.endsWith(I18nMessageUtil.getMessage(CommonConstant.ZHUANG_AUTONOMUS_REGION))){
//                    boolQueryBuilder.filter(termQuery(provinceAttr, province.replaceAll(I18nMessageUtil.getMessage(CommonConstant.ZHUANG_AUTONOMUS_REGION),"")));
//                }else if (province.endsWith(I18nMessageUtil.getMessage(CommonConstant.HUI_AUTONOMOUS_REGION))){
//                    boolQueryBuilder.filter(termQuery(provinceAttr, province.replaceAll(I18nMessageUtil.getMessage(CommonConstant.HUI_AUTONOMOUS_REGION),"")));
//                }else if (province.endsWith(I18nMessageUtil.getMessage(CommonConstant.PROVINCE))){
//                    boolQueryBuilder.filter(termQuery(provinceAttr, province));
                }else if(province.contains(I18nMessageUtil.getMessage(CommonConstant.PROVINCE))) {
                    boolQueryBuilder.filter(termQuery(provinceAttr, province ));
                }else {
                    boolQueryBuilder.filter(termQuery(provinceAttr, province + I18nMessageUtil.getMessage(CommonConstant.PROVINCE)));
                }
                termsAggregationBuilder = AggregationBuilders.terms("aggs01").field(cityAttr);
            }
        }
        //该桶要置为最大
        termsAggregationBuilder.size(Integer.MAX_VALUE);
        termsAggregationBuilder.executionHint(ElasticConstant.EXECUTION_HINT);
        JSONObject resultJsonObject = esHelper.elasticAggs4Buckets2Object(indexTypeEnum, boolQueryBuilder, termsAggregationBuilder);
        JSONArray bucketsArray = resultJsonObject.getJSONArray("buckets");

        List<ResultDataSeriesChartVO> resultDataSeriesChartVOList = new ArrayList<>();
        for (Object object : bucketsArray) {
            if (object != null) {
                JSONObject dataJsonObject = (JSONObject) object;
                //该key为省或市的名称
                String name = dataJsonObject.getString("key");
//                if (MapConstant.AUTONOMOUS_REGION.contains(name)) {
//                    name = name + I18nMessageUtil.getMessage(CommonConstant.AUTONOMOUS_REGION);
//                } else if (MapConstant.SPECIAL_ADMINISTRATIVE_REGION.contains(name)){
//                    name = name + I18nMessageUtil.getMessage(CommonConstant.SPECIAL_ADMINISTRATIVE_REGION);
//                }else if (MapConstant.GUANG_XI.equals(name)){
//                    name = name + I18nMessageUtil.getMessage(CommonConstant.ZHUANG_AUTONOMUS_REGION);
//                }else if (MapConstant.NING_XIA.equals(name)){
//                    name = name + I18nMessageUtil.getMessage(CommonConstant.HUI_AUTONOMOUS_REGION);
//                }
                if (!StringUtil.isEmpty(name)) {
                    //没有选省市区的不画
//                    if (bool) {
//                        name = name.replace(I18nMessageUtil.getMessage(CommonConstant.PROVINCE), "").replace(I18nMessageUtil.getMessage(CommonConstant.CITY), "");
//                    }
                    String value = dataJsonObject.getString("doc_count");

                    ResultDataSeriesChartVO resultDataSeriesChartVO = new ResultDataSeriesChartVO();
                    resultDataSeriesChartVO.setName(name);
                    resultDataSeriesChartVO.setValue(String.valueOf(value));
                    resultDataSeriesChartVOList.add(resultDataSeriesChartVO);
                }
            }
        }
        return resultDataSeriesChartVOList;
    }

    /**
     * 处理环比、同比的最终结果
     *
     * @param objectNow  当前时间的结果
     * @param objectLast 环比、同比结果
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author 徐俊杰
     * @date 2019/2/28 8:46
     * @since v1.0
     */
    public ElasticAggsPojo getMomYoyResult(ElasticAggsPojo objectNow, ElasticAggsPojo objectLast) {
        //现在的数据：用double型vo接收，因为是算比例，所以统计结果格式化为double去计算
        ResultDoubleListVO nowVo = getResultDoubleListVO(objectNow);
        //过去用来比较的数据：用double型vo接收，因为是算比例，所以统计结果格式化为double去计算
        ResultDoubleListVO lastVO = getResultDoubleListVO(objectLast);
        //取出内部数值进行处理
        Double valueNowTotal = nowVo.getTotal();
        Double valueLastTotal = lastVO.getTotal();
        List<Double> valueNowList = nowVo.getResultList();
        List<Double> valueLastList = lastVO.getResultList();
        //对total值进行处理
        Double compareRateTotal = ChartResultUtil.getMomYoyRate(valueNowTotal, valueLastTotal);
        //获取一次buckets
        List<AggsBucketsPojo> bucketsNow = objectNow.getBuckets();
        //结果比率的数据
        ElasticAggsPojo elasticAggsPojo = new ElasticAggsPojo();
        elasticAggsPojo.setTotal(compareRateTotal + "%");
        //对桶内的值进行处理
        List<AggsBucketsPojo> rateList = elasticAggsPojo.getBuckets();
        for (int i = 0; i < valueNowList.size(); i++) {
            Double valueNow = valueNowList.get(i);
            Double valueLast = valueLastList.get(i);
            Double compareRate = ChartResultUtil.getMomYoyRate(valueNow, valueLast);
            AggsBucketsPojo aggsBucketsPojo = bucketsNow.get(i);
            AggsBucketsPojo ratePojo = new AggsBucketsPojo(aggsBucketsPojo.getKey(), compareRate + "%");
            rateList.add(ratePojo);
        }
        elasticAggsPojo.setBuckets(rateList);
        return elasticAggsPojo;
    }

    /**
     * 获取开启业绩分配的统计结果（非pk榜）
     * @param chartResultPojo
     * @param perforList      业绩分配list（内部time及金额已经做过处理）
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author zcp
     * @date 2019/3/7 14:59
     * @since v1.0
     * @version v1.0
     */
    public ElasticAggsPojo getResult4OpenPerforRule(ChartResultPojo chartResultPojo, List<ContractPerformanceInfoVO> perforList) {
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        int timeType = fiscalYearTimePojo.getTimeType4DefaultMonth();
        //1 本身求值类型就是单个值、图表类型是数值VALUE型的；2 非数值型自定义时间筛选，不能聚合，需对结果处理成数组；3数组，聚合分桶
        ReturnTypeEnum returnTypeEnum = checkReturn(ValueTypeEnum.ARRAY_TYPE, chartEntity, timeType);
        boolean flag = checkReturnFlag(returnTypeEnum);
        //返回数值的，无需聚合，否则需要置入聚合条件
        double total = 0;
        if (flag) {
            for (ContractPerformanceInfoVO contractPerformanceInfoVO : perforList) {
                total += contractPerformanceInfoVO.getAmount();
            }
            total = Arith.div(total, 1, 2);
            return formatResult4Total(returnTypeEnum, String.valueOf(total));
        } else {
            TimeAmountPojo timeAmountPojo = new TimeAmountPojo();
            //签订时间list
            List<Integer> timeList = timeAmountPojo.getTimeList();
            //金额list
            List<Double> amountList = timeAmountPojo.getAmountList();
            for (ContractPerformanceInfoVO contractPerformanceInfoVO : perforList) {
                Integer signTime = contractPerformanceInfoVO.getTime();
                Double amount = contractPerformanceInfoVO.getAmount();
                timeList.add(signTime);
                //保留2位小数
                amount = Arith.div(amount, 1, 2);
                amountList.add(amount);
                total += amount;
            }
            timeAmountPojo.setTimeList(timeList);
            timeAmountPojo.setAmountList(amountList);
            ElasticAggsPojo elasticAggsPojo = getAmountList(chartResultPojo.getStartTimeList(), chartResultPojo.getEndTimeList(), timeAmountPojo);
            elasticAggsPojo.setTotal(String.valueOf(total));
            return elasticAggsPojo;
        }
    }

    /**
     * 计算各个时间段的金额
     * @param startTimeList  开始时间list
     * @param endTimeList    结束时间list
     * @param timeAmountPojo 包含两个list（大小一致，顺序对应）：timeList，amountList
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author zcp
     * @date 2019/3/6 21:01
     * @since v1.0
     * @version v1.0
     */
    public ElasticAggsPojo getAmountList(List<Integer> startTimeList, List<Integer> endTimeList, TimeAmountPojo timeAmountPojo) {
        ElasticAggsPojo elasticAggsPojo = new ElasticAggsPojo();
        List<AggsBucketsPojo> aggsBucketsPojoList = elasticAggsPojo.getBuckets();
        for (int i = 0; i < startTimeList.size(); i++) {
            Integer startTime = startTimeList.get(i);
            Integer endTime = endTimeList.get(i);

            Double amount = getAmountByTime(startTime, endTime, timeAmountPojo);
            //保留2位小数
            amount = Arith.div(amount, 1, 2);
            AggsBucketsPojo aggsBucketsPojo = new AggsBucketsPojo("", String.valueOf(amount));
            aggsBucketsPojoList.add(aggsBucketsPojo);
        }
        elasticAggsPojo.setBuckets(aggsBucketsPojoList);
        return elasticAggsPojo;
    }

    /**
     * 对查出的结果列表内相同员工的数据进行汇总
     * @param perforList 业绩分配list（内部time及金额已经做过处理）
     * @return java.util.List<com.alibaba.fastjson.JSONObject>
     * @author zcp
     * @date 2019/3/20 17:01
     * @since v1.0
     * @version v1.0
     */
    public List<JSONObject> groupUserAmount4Perfor(List<ContractPerformanceInfoVO> perforList) {
        //userId--amount
        Map<String, Double> amountUserMap = new HashMap<>(perforList.size());
        //对同一个员工的求和
        for (ContractPerformanceInfoVO contractPerformanceInfoVO : perforList) {
            String userId = contractPerformanceInfoVO.getUserId();
            Double amount = contractPerformanceInfoVO.getAmount();
            Double value = amountUserMap.get(userId);
            if (value == null) {
                value = 0D;
            }
            value += amount;
            amountUserMap.put(userId, value);
        }
        List<JSONObject> list = new ArrayList<>();
        for (Map.Entry<String, Double> entry : amountUserMap.entrySet()) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("userId", entry.getKey());
            jsonObject.put("value", entry.getValue());
            list.add(jsonObject);
        }
        return list;
    }

    /**
     * 得到合同id--币种汇率对应关系map
     * @param contractInfoVOList 合同list
     * @param currencyRateMap    币种id--币种汇率
     * @return java.util.Map<java.lang.Long   ,   java.lang.Double>
     * @author zcp
     * @date 2019/3/20 21:32
     * @since v1.0
     * @version v1.0
     */
    public Map<Long, Double> getExchangeRateMapFromContract(List<ContractInfoVO> contractInfoVOList, Map<Long, Double> currencyRateMap) {
        if (contractInfoVOList == null || currencyRateMap == null) {
            return new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        }
        //合同id--币种汇率对应关系map
        Map<Long, Double> cidRateMap = new HashMap<>(contractInfoVOList.size());
        for (ContractInfoVO contractInfoVO : contractInfoVOList) {
            double rate = BasicHelper.getExchangeRateByCurrency(contractInfoVO.getCurrency(), currencyRateMap);
            cidRateMap.put(contractInfoVO.getId(), rate);
        }

        return cidRateMap;
    }

    /**
     * 封装图表中心环比、同比图：包含当月、上月、去年当月、环比、同比
     * @param chartResultPojo   统计参数pojo
     * @param legendList        图的legend
     * @param unitList          各条线的单位，与legendList大小一致
     * @param thisMonthAggsPojo 当月额
     * @param lastMonthAggsPojo 上月额
     * @param lastYearAggsPojo  去年当月额
     * @param resultMom         环比
     * @param resultYoy         同比
     * @return java.util.List<com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultInfoDTO>
     * @author zcp
     * @date 2019/4/23 16:07
     * @since v1.0
     * @version v1.0
     */
    public List<StatisticResultInfoDTO> getInfoDtos4MomYoy(ChartResultPojo chartResultPojo, List<String> legendList, List<String> unitList, ElasticAggsPojo thisMonthAggsPojo, ElasticAggsPojo lastMonthAggsPojo, ElasticAggsPojo lastYearAggsPojo, ElasticAggsPojo resultMom, ElasticAggsPojo resultYoy) {
        //只有当月穿透，移动端产品销量不穿透
        ThroughDTO throughDTO = null;
        SystemCodeEnum systemCodeEnum = chartResultPojo.getSystemCodeEnum();
        if (throughHelp.checkThrough(chartResultPojo, systemCodeEnum)) {
            throughDTO = new ThroughDTO(chartResultPojo, systemCodeEnum);
        }
        String unit = chartResultPojo.getUnit();
        //是否画入表格内 0否1是
        Integer tableFlag = 1;
        //分别采用两个不同单位的y轴，默认为yAxisIndex: 0，当为1时，代表另外一个y坐标轴
        Integer yAxisIndex = 0;
        //1隐藏，0不隐藏
        Integer hide = 0;
        //--------------------y轴左侧--------------------
        //当月
        StatisticResultInfoDTO thisStatisticResultInfoDto = new StatisticResultInfoDTO(throughDTO, tableFlag, yAxisIndex, hide, legendList.get(0), unit, thisMonthAggsPojo);
        //上月
        StatisticResultInfoDTO lastMonthStatisticResultInfoDto = new StatisticResultInfoDTO(null, tableFlag, yAxisIndex, hide, legendList.get(1), unit, lastMonthAggsPojo);
        //去年当月
        StatisticResultInfoDTO lastYearStatisticResultInfoDto = new StatisticResultInfoDTO(null, tableFlag, yAxisIndex, hide, legendList.get(2), unit, lastYearAggsPojo);
        //--------------------y轴右侧--------------------
        yAxisIndex = 1;
        hide = 1;
        //环比、同比，必须强制置为折线类型
        StatisticResultInfoDTO momStatisticResultInfoDto = new StatisticResultInfoDTO(null, tableFlag, yAxisIndex, hide, legendList.get(3), getUnit(unitList.get(3)), resultMom);
        momStatisticResultInfoDto.setChartType(ChartTypeEnum.LINE_CHART.getCode());
        StatisticResultInfoDTO yoyStatisticResultInfoDto = new StatisticResultInfoDTO(null, tableFlag, yAxisIndex, hide, legendList.get(4), getUnit(unitList.get(4)), resultYoy);
        yoyStatisticResultInfoDto.setChartType(ChartTypeEnum.LINE_CHART.getCode());

        return Arrays.asList(thisStatisticResultInfoDto, lastMonthStatisticResultInfoDto, lastYearStatisticResultInfoDto, momStatisticResultInfoDto, yoyStatisticResultInfoDto);
    }

    /**
     * 获取部分/总体的比例
     * 注，确保传入的两个对象大小类型一致
     *
     * @param partPojo 部分（分子）
     * @param allPojo  总体（分母）
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author 徐俊杰
     * @date 2019/3/27 10:10
     * @since v1.0
     * @update 2019-4-23 从JxcProductionOrderResult移入，并优化方法实现，去除多余循环 by zcp
     */
    public ElasticAggsPojo getRateResult4Int(ElasticAggsPojo partPojo, ElasticAggsPojo allPojo) {
        //返回的比例pojo
        ElasticAggsPojo ratePojo = new ElasticAggsPojo();
        List<AggsBucketsPojo> partAggsBucketsPojo = partPojo.getBuckets();
        List<AggsBucketsPojo> allAggsBucketsPojo = allPojo.getBuckets();
        //设置分组的比例值
        List<AggsBucketsPojo> rateList = ratePojo.getBuckets();
        for (int i = 0; i < partAggsBucketsPojo.size(); i++) {
            Integer part = StringUtil.toInt(partAggsBucketsPojo.get(i).getValue(), 0);
            Integer all = StringUtil.toInt(allAggsBucketsPojo.get(i).getValue(), 0);
            Double rate = ChartResultUtil.getRate(part, all);
            AggsBucketsPojo rateBucketsPojo = new AggsBucketsPojo("", rate + "%");
            rateList.add(rateBucketsPojo);
        }
        ratePojo.setBuckets(rateList);
        return ratePojo;
    }

    /**
     * 生成指标的缓存key
     * <p>
     * 生成规则：固定前缀 + md5后的sysStatisticsKey
     * 具体有两种：（括号内代表不一定有）
     * （1）超管角色：corpid_systemCode（_valueTypeEnum）_timeType值_startTime值_endTime值_companyStructType值（筛选部门or员工）_筛选的部门id或员工id（checkedDepId、checkedUserId）_其他筛选条件（如formId、地图的省市区筛选）
     * （2）非超管角色：corpid_userId_systemCode（_valueTypeEnum）_timeType值_startTime值_endTime值_companyStructType值（筛选部门or员工）_筛选的部门id或员工id（checkedDepId、checkedUserId）_其他筛选条件（如formId、地图的省市区筛选）
     *
     * 注：RedisPrefixConstant.SYS_STATISTICS作为PaasRedisHelper.getValue和setValue的参数传入即可，不在该方法内拼接
     *
     * @param chartResultRedisPojo 图表统计结果缓存pojo；
     *                             生成缓存key主要看该pojo的字段值，可根据其构造方法查看各指标使用情况；
     *                             其中chartResultBasePojo为必置入的字段，valueTypeEnum、otherSuffix等非必传
     * @return java.lang.String
     * @author zcp
     * @version v1.0
     * @since v1.0
     */
    public String createSysStatisticsRedisKey(ChartResultRedisPojo chartResultRedisPojo) {
        ChartResultBasePojo chartResultBasePojo = chartResultRedisPojo.getChartResultBasePojo();
        SystemCodeEnum systemCodeEnum = chartResultRedisPojo.getSystemCodeEnum();
        ValueTypeEnum valueTypeEnum = chartResultRedisPojo.getValueTypeEnum();
        String otherSuffix = chartResultRedisPojo.getOtherSuffix();
        ChartEntity chartEntity = chartResultRedisPojo.getChartResultBasePojo().getChartEntity();
        chartEntity = chartEntity == null ? new ChartEntity() : chartEntity;
        Long departmentPkId = chartResultRedisPojo.getChartResultBasePojo().getDepartmentPkId();
        // 为了获取pkType，兼容之前逻辑，强转失败就没办法了
        ChartResultPkPojo chartResultPkPojo = chartResultRedisPojo.getChartResultBasePojo() instanceof ChartResultPkPojo ? (ChartResultPkPojo) chartResultRedisPojo.getChartResultBasePojo() : null;
        //------------------固定前缀：corpid（_userId）_
        StringBuilder keyPrefix = new StringBuilder();
        keyPrefix.append(chartResultBasePojo.getCorpid());
        UserVO loginUser = chartResultBasePojo.getLoginUser();
        if (Objects.isNull(loginUser) || !loginUser.isMaxDataPermission()) {
            //loginUser为空，或者非超管拼上登录的userId；超管角色（相同筛选条件）整个公司公用一份
            keyPrefix.append(SymbolConstant.UNDERLINE);
            keyPrefix.append(chartResultBasePojo.getUserId());
        }
        keyPrefix.append(SymbolConstant.UNDERLINE);
        //------------------以下需要做md5后再拼到prefix上面
        StringBuilder sysStatisticsKey = new StringBuilder();
        // systemCode
        if (Objects.nonNull(systemCodeEnum)) {
            sysStatisticsKey.append(systemCodeEnum.getSystemCode());
        }
        // 自定义pk没有systemCode，用chartId区分
        if (Objects.equals(chartEntity.getStatisticsType(), StatisticsTypeEnum.CUSTOM_STATISTIC.getCode())) {
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(chartEntity.getId());
        }
        //_valueTypeEnum
        if (Objects.nonNull(valueTypeEnum)) {
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(valueTypeEnum.getCode());
        }
        // chartResultPkPojo
        if (Objects.nonNull(chartResultPkPojo)) {
            // 放入pkType,避免回显列表人员和部门key冲突
            if (Objects.nonNull(chartResultPkPojo.getPkType())) {
                sysStatisticsKey.append(SymbolConstant.UNDERLINE);
                sysStatisticsKey.append(chartResultPkPojo.getPkType());
            }
            // 放入top数，避免首页top3冲突
            if (Objects.nonNull(chartResultPkPojo.getTop())) {
                sysStatisticsKey.append(SymbolConstant.UNDERLINE);
                sysStatisticsKey.append(chartResultPkPojo.getTop());
            }
        }
        // 放入departmentPkId,避免部门pk冲突
        if (Objects.nonNull(departmentPkId)) {
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(departmentPkId);
        }
        //_timeType值_startTime值_endTime值
        FiscalYearTimePojo fiscalYearTimePojo = chartResultBasePojo.getFiscalYearTimePojo();
        if (Objects.nonNull(fiscalYearTimePojo)) {
            if (Objects.nonNull(fiscalYearTimePojo.getTimeType())) {
                sysStatisticsKey.append(SymbolConstant.UNDERLINE);
                sysStatisticsKey.append(fiscalYearTimePojo.getTimeType());
            } else if (Objects.nonNull(fiscalYearTimePojo.getTimeTypeEnum())) {
                sysStatisticsKey.append(SymbolConstant.UNDERLINE);
                sysStatisticsKey.append(fiscalYearTimePojo.getTimeTypeEnum().getCode());
            }
        }
        sysStatisticsKey.append(SymbolConstant.UNDERLINE);
        sysStatisticsKey.append(chartResultBasePojo.getStartTime());
        sysStatisticsKey.append(SymbolConstant.UNDERLINE);
        sysStatisticsKey.append(chartResultBasePojo.getEndTime());

        //_companyStructType值（筛选部门or员工）_筛选的部门id或员工id（checkedDepId、checkedUserId）
        // @update 现在筛选条件可以为多选，则如果是多选，则格式为”_3_筛选的员工id1_筛选的员工id2_筛选的员工id3...“
        Long checkedDepId = chartResultBasePojo.getCheckedDepId();
        String checkedUserId = chartResultBasePojo.getCheckedUserId();
        List<Long> checkedDepIdIn = chartResultBasePojo.getCheckedDepIdIn();
        List<String> checkedUserIdIn = chartResultBasePojo.getCheckedUserIdIn();
        CompanyStructTypeEnum companyStructTypeEnum = chartDtoHelp.ifCheckDepOrUser(checkedDepId, checkedUserId,chartResultBasePojo.getCheckedDepIdIn(),chartResultBasePojo.getCheckedUserIdIn());
        sysStatisticsKey.append(SymbolConstant.UNDERLINE);
        sysStatisticsKey.append(companyStructTypeEnum.getCode());
        switch (companyStructTypeEnum) {
            case USER:
                if(CollectionsUtil.isEmpty(checkedUserIdIn)){
                    sysStatisticsKey.append(SymbolConstant.UNDERLINE);
                    sysStatisticsKey.append(checkedUserId);
                }else{
                    sysStatisticsKey.append(SymbolConstant.UNDERLINE);
                    sysStatisticsKey.append(splicRedisKeyByChecked(checkedUserIdIn,null));
                }
                break;
            case DEPARTMENT:
                if(CollectionsUtil.isEmpty(checkedDepIdIn)){
                    sysStatisticsKey.append(SymbolConstant.UNDERLINE);
                    sysStatisticsKey.append(checkedDepId);
                }else{
                    sysStatisticsKey.append(SymbolConstant.UNDERLINE);
                    sysStatisticsKey.append(splicRedisKeyByChecked(null,checkedDepIdIn));
                }
                break;
            case ALL:
                sysStatisticsKey.append(SymbolConstant.UNDERLINE);
                sysStatisticsKey.append(splicRedisKeyByChecked(checkedUserIdIn,checkedDepIdIn));
                break;
            default:
                break;
        }
        //其他筛选条件（如formId、地图的省市区筛选）
        if (Objects.nonNull(chartResultBasePojo.getRefId())) {
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(chartResultBasePojo.getRefId());
        }
        if (Objects.nonNull(chartResultBasePojo.getArgTwoId())) {
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(chartResultBasePojo.getArgTwoId());
        }
        if (StringUtil.isNotEmpty(chartResultBasePojo.getProvince())) {
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(chartResultBasePojo.getProvince());
        }
        if (StringUtil.isNotEmpty(otherSuffix)) {
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(otherSuffix);
        }
        //图表中心业绩目标翻页
        if(Objects.nonNull(chartResultBasePojo.getPage())){
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(chartResultBasePojo.getPage());
        }
        if(Objects.nonNull(chartResultBasePojo.getPageSize())){
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(chartResultBasePojo.getPageSize());
        }
        //避免pk榜切换数据单位之后读到缓存的旧值
        if(Objects.nonNull(chartEntity.getNumFormat())){
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(chartEntity.getNumFormat());
        }
        //pk首页`全公司top3开关`
        if(Objects.nonNull(chartResultBasePojo.getTop3Switch())){
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(chartResultBasePojo.getTop3Switch());
        }
        //pk可见范围
        if(Objects.nonNull(chartEntity.getPowerType())){
            sysStatisticsKey.append(SymbolConstant.UNDERLINE);
            sysStatisticsKey.append(chartEntity.getPowerType());
        }
        //------------------最终系统指标缓存key
        String sysStatisticsKeyMd5 = MD5Util.EncoderByMd5(sysStatisticsKey.toString());
        keyPrefix.append(sysStatisticsKeyMd5);
        return keyPrefix.toString();
    }

    /**
     * 拼接部门多选的key
     *
     * @param checkedUserIdIn
     * @param checkedDepIdIn
     * @return boolean
     * @author zyy
     * @date 2022/6/7
     * @version v1.0
     */
    private String splicRedisKeyByChecked(List<String> checkedUserIdIn, List<Long> checkedDepIdIn) {
        StringBuilder redisKey = new StringBuilder();
        //后缀是否已经拼接 _
        boolean bool = true;
        if(!CollectionsUtil.isEmpty(checkedUserIdIn)){
            bool = false;
            Iterator<String> iterator = checkedUserIdIn.iterator();
            while (iterator.hasNext()){
                redisKey.append(iterator.next());
                if(iterator.hasNext()){
                    redisKey.append(SymbolConstant.UNDERLINE);
                }
            }
        }
        if(!CollectionsUtil.isEmpty(checkedDepIdIn)){
            if(!bool){
                redisKey.append(SymbolConstant.UNDERLINE);
            }
            Iterator<Long> iterator = checkedDepIdIn.iterator();
            while (iterator.hasNext()){
                redisKey.append(iterator.next());
                if(iterator.hasNext()){
                    redisKey.append(SymbolConstant.UNDERLINE);
                }
            }
        }
        return redisKey.toString();
    }

    //-------------------private方法-------------------

    /**
     * 将集合元素按照指定规则拼接
     * @param list 待处理的集合
     * @return 拼接好的字符串（格式：element1_element2_element3）
     * @author 梅伟丁
     * @date 2022/5/12 14:49:16
     * @version 1.0
     */
    private String handleListToStr(List<?> list) {
        StringBuilder str = new StringBuilder();
        Iterator<?> iterator = list.iterator();
        while (iterator.hasNext()){
            str.append(iterator.next());
            if (iterator.hasNext()){
                str.append(SymbolConstant.UNDERLINE);
            }
        }
        return str.toString();
    }

    /**
     * 判断求值的类型
     * @param valueTypeEnum 值类型
     * @param chartEntity   图实体
     * @param timeType      时间筛选类型，对应TimeTypeEnum枚举
     * @return com.xbongbong.pro.statistic.enums.ReturnTypeEnum 系统返回值类型枚举
     * @author zcp
     * @date 2019/2/21 10:15
     * @since v1.0
     * @version v1.0
     */
    private ReturnTypeEnum checkReturn(ValueTypeEnum valueTypeEnum, ChartEntity chartEntity, int timeType) {
        //本身求值是单个值，或者图表类型也是数值，则先处理成单个值
        boolean valueFlag = valueTypeEnum == ValueTypeEnum.VALUE_TYPE || (chartEntity != null && Objects.equals(chartEntity.getChartType(), ChartTypeEnum.VALUE.getCode()));
        if (valueFlag) {
            //只获取总数即可，无需做其他处理
            return ReturnTypeEnum.VALUE_RETURN;
        }
        if (timeType == TimeTypeEnum.CUSTOM.getCode() || timeType == TimeTypeEnum.DYNAMIC.getCode() || timeType == TimeTypeEnum.UP_TO_NOW.getCode()) {
            //返回数组，但是不能时间分桶（无法设置interval），只能求出一个总数，然后内存处理成数组型（手动封装成一个大桶）
            return ReturnTypeEnum.CUSTOM_RETURN;
        }
        //返回数组，且要时间分桶
        return ReturnTypeEnum.ARRAY_RETURN;
    }

    /**
     * 判断返回值的类型
     * @param returnTypeEnum com.xbongbong.pro.statistic.enums.ReturnTypeEnum
     * @return boolean true本身求值是单个值，或者图表类型也是数值；false 需聚合出数组型（时间分桶等）
     * @author zcp
     * @date 2019/2/21 10:36
     * @since v1.0
     * @version v1.0
     */
    private boolean checkReturnFlag(ReturnTypeEnum returnTypeEnum) {
        return returnTypeEnum == ReturnTypeEnum.VALUE_RETURN || returnTypeEnum == ReturnTypeEnum.CUSTOM_RETURN;
    }

    /**
     * 当非数值型，请求自定义时间类型时，把结果处理成一个大桶
     * @param returnTypeEnum  系统指标返回值类型，只有当returnTypeEnum为CUSTOM_RETURN类型时才处理
     * @param elasticAggsPojo 已经计算好的结果
     * @return com.xbongbong.paas.elasticsearch.pojo.vo.ElasticAggsPojo
     * @author zcp
     * @date 2019/2/21 10:44
     * @since v1.0
     * @version v1.0
     */
    private ElasticAggsPojo formatResult4Custom(ReturnTypeEnum returnTypeEnum, ElasticAggsPojo elasticAggsPojo) {
        //非数值型自定义时间，需要处理桶内结果
        if (returnTypeEnum == ReturnTypeEnum.CUSTOM_RETURN) {
            List<AggsBucketsPojo> buckets = elasticAggsPojo.getBuckets();
            AggsBucketsPojo aggsBucketsPojo = new AggsBucketsPojo("", elasticAggsPojo.getTotal());
            buckets.add(aggsBucketsPojo);
            elasticAggsPojo.setBuckets(buckets);
        }
        return elasticAggsPojo;
    }

    /**
     * 封装总数的返回值
     * @param returnTypeEnum
     * @param total
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author zcp
     * @date 2019/2/28 18:17
     * @since v1.0
     * @version v1.0
     */
    private ElasticAggsPojo formatResult4Total(ReturnTypeEnum returnTypeEnum, String total) {
        /*--------得到聚合结果--------*/
        ElasticAggsPojo elasticAggsPojo = new ElasticAggsPojo();
        elasticAggsPojo.setTotal(total);
        if (returnTypeEnum == ReturnTypeEnum.CUSTOM_RETURN) {
            List<AggsBucketsPojo> buckets = elasticAggsPojo.getBuckets();
            //图内不需要逗号
            AggsBucketsPojo aggsBucketsPojo = new AggsBucketsPojo("", total);
            buckets.add(aggsBucketsPojo);
            elasticAggsPojo.setBuckets(buckets);
        }
        return elasticAggsPojo;
    }

    /**
     * 处理各个桶内数据：
     * （1）超过未来时间不现实，置为“-”
     * （2）把处理后的value置入图和表的相应对象
     * （3）处理穿透参数
     * @param elasticAggsPojo 统计结果pojo
     * @param chartResultPojo 参数pojo
     * @param now             当前时间（当未来时间不处理时，用于与当前时间比较）
     * @param valueArray      图需要的数据数组
     * @author zcp
     * @date 2019/2/20 14:30
     * @since v1.0
     * @version v1.0
     */
    private List<ResultDataTableVO> formatValue(ThroughDTO throughDTO, ElasticAggsPojo elasticAggsPojo, ChartResultPojo chartResultPojo, Integer now, JSONArray valueArray) throws XbbException {
        List<ResultDataTableVO> resultDataTableVoList = new ArrayList<>();
        List<AggsBucketsPojo> buckets = elasticAggsPojo.getBuckets();
        if (buckets == null) {
            return resultDataTableVoList;
        }
        //不计算未来时间---startTimeList的大小和buckets对应
        List<Integer> startTimeList = chartResultPojo.getStartTimeList();
        List<Integer> endTimeList = chartResultPojo.getEndTimeList();
        FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
        boolean splitFlag = true;
        NumFormatEnum numFormatEnum = NumFormatEnum.getByCode(chartResultPojo.getNumFormat());
        for (int j = 0; j < buckets.size(); j++) {
            AggsBucketsPojo aggsBucketsPojo = buckets.get(j);
            String value = aggsBucketsPojo.getValue();
            String key = aggsBucketsPojo.getKey();
            //未来时间是否计算（如客户总数折线图，超过当前时间的，都用“--”标识，不再计算数量） 0否 1是，默认1计算
            Integer futureCalc = chartResultPojo.getFutureCalc();
            ResultDataTableVO resultDataTableVo;
            String valueChart;
            Integer time;
            try {
                time = startTimeList.get(j);
            } catch (IndexOutOfBoundsException e) {
                throw new XbbException(StatisticsServicesErrorCodeEnum.API_ERROR_224065);
            }
            if (futureCalc != null && futureCalc == 0 && time > now) {
                valueChart = "-";
                resultDataTableVo = new ResultDataTableVO(valueChart, null);
            } else {
                valueChart = ChartResultUtil.formatValue2String(true, chartResultPojo.getValueTypeEnum(), false, value, numFormatEnum, chartResultPojo.getNumFormatFlag());
                String valueTable = ChartResultUtil.formatValue2String(false, chartResultPojo.getValueTypeEnum(), false, value, numFormatEnum, chartResultPojo.getNumFormatFlag());
                Integer endTime = endTimeList.get(j);
                if (Objects.nonNull(throughDTO) && Objects.equals(chartResultPojo.getChartEntity().getSystemCode(), SystemCodeEnum.CLUE_SOURCE.getSystemCode())) {
                    if (StringUtil.isNotEmpty(key)) {
                        throughDTO.setDictionaryCodeThrough(key);
                    } else {
                        throughDTO.setDictionaryCodeThrough("other");
                    }
                    TimePojo timePojo = throughHelp.formatThroughTimeInfo(false, chartResultPojo.getFiscalYearTimePojo(), chartResultPojo.getStartTime(), chartResultPojo.getEndTime());
                    resultDataTableVo = throughHelp.formatResultDataTableVO(valueTable, throughDTO, timePojo);
                }else{
                    TimePojo timePojo = throughHelp.formatThroughTimeInfo(splitFlag, fiscalYearTimePojo, time, endTime);
                    resultDataTableVo = throughHelp.formatResultDataTableVO(valueTable, throughDTO, timePojo);
                }
            }
            valueArray.add(valueChart);
            //表数据塞入resultDataTableVO
            resultDataTableVoList.add(resultDataTableVo);
        }
        return resultDataTableVoList;
    }

    /**
     * 处理首次、再次成交客户的pojo
     * @param bucketsArray              聚合（客户id分组聚合）结果array
     * @param agoToStartCustomerIdIn    存放历史成交的客户ids（已经与某个模版的客户ids取过交集）
     * @param customerOkFirstSecondPojo 首次成交、再次成交pojo
     * @param firstResultList           各个桶内，首次成交list
     * @param secondResultList          各个桶内，再次成交list
     * @param firstCustomerIds          各个桶内，首次成交客户id list
     * @param secondCustomerIds         各个桶内，再次成交客户id list
     * @return com.xbongbong.pro.statistic.pojo.CustomerOkFirstSecondPojo
     * @author zcp
     * @date 2019/2/22 21:31
     * @since v1.0
     * @version v1.0
     */
    private CustomerOkFirstSecondPojo formatFirstAndSecondPojo(JSONArray bucketsArray, Set<Long> agoToStartCustomerIdIn, CustomerOkFirstSecondPojo customerOkFirstSecondPojo, List<Integer> firstResultList, List<Integer> secondResultList, List<Set<Long>> firstCustomerIds, List<Set<Long>> secondCustomerIds) {
        //首次成交总数
        int firstTotal = customerOkFirstSecondPojo.getFirstTotal();
        //再次成交总数
        int secondTotal = customerOkFirstSecondPojo.getSecondTotal();
        //总桶内，再次成交客户id 集合
        Set<Long> secondCids = customerOkFirstSecondPojo.getSecondCids();
        //首次成交
        Integer innerFirstTotal = 0;
        //再次成交
        Integer innerSecondTotal = 0;
        //首次成交客户id list
        Set<Long> innerFirstCustomerIds = new HashSet<>();
        //再次成交客户id list
        Set<Long> innerSecondCustomerIds = new HashSet<>();
        for (Object object : bucketsArray) {
            if (object != null) {
                JSONObject innerJson = (JSONObject) object;
                Long customerId = innerJson.getLong("key");
                if (customerId == null || customerId == 0) {
                    continue;
                }
                Integer count = innerJson.getInteger("doc_count");
                int countInt = count == null ? 0 : count;
                boolean flag = (countInt == 0 || countInt == 1) && !agoToStartCustomerIdIn.contains(customerId);
                if (flag) {
                    innerFirstTotal += 1;
                    firstTotal += 1;
                    innerFirstCustomerIds.add(customerId);
                } else {
                    innerSecondTotal += 1;
                    innerSecondCustomerIds.add(customerId);
                    //各个桶内的复购客户不能重复求和
                    if (!secondCids.contains(customerId)) {
                        secondTotal += 1;
                    }
                    secondCids.add(customerId);
                }
                //成交客户id
                agoToStartCustomerIdIn.add(customerId);
            }
        }
        firstResultList.add(innerFirstTotal);
        secondResultList.add(innerSecondTotal);
        firstCustomerIds.add(innerFirstCustomerIds);
        secondCustomerIds.add(innerSecondCustomerIds);
        customerOkFirstSecondPojo.setFirstTotal(firstTotal);
        customerOkFirstSecondPojo.setSecondTotal(secondTotal);
        return customerOkFirstSecondPojo;
    }
    /**
     * 处理首次、再次成交客户的pojo
     * 首次、多次的分开塞到不同集合 firstCustomerIds、secondCustomerIds，然后把所有首次成交的汇总塞入firstCids（即只有时间区间内首次成交的客户需要再次查询验证startTime之前是否签过合同）
     * @param bucketsArray              聚合（客户id分组聚合）结果array
     * @param customerOkFirstSecondPojo 首次成交、再次成交pojo
     * @param firstCustomerIds          各个桶内，首次成交客户id list
     * @param secondCustomerIds         各个桶内，再次成交客户id list
     * @return com.xbongbong.pro.statistic.pojo.CustomerOkFirstSecondPojo
     * @author zcp
     * @date 2019/2/22 21:31
     * @since v1.0
     * @version v1.0
     */
    private CustomerOkFirstSecondPojo formatFirstAndSecondPojoRefactor(JSONArray bucketsArray, CustomerOkFirstSecondPojo customerOkFirstSecondPojo, List<Set<Long>> firstCustomerIds, List<Set<Long>> secondCustomerIds) {
        //总桶内，首次成交客户id 集合
        Set<Long> firstCids = customerOkFirstSecondPojo.getFirstCids();
        //总桶内，再次成交客户id 集合
        Set<Long> secondCids = customerOkFirstSecondPojo.getSecondCids();
        //首次成交客户id list
        Set<Long> innerFirstCustomerIds = new HashSet<>();
        //再次成交客户id list
        Set<Long> innerSecondCustomerIds = new HashSet<>();
        for (Object object : bucketsArray) {
            if (object != null) {
                JSONObject innerJson = (JSONObject) object;
                Long customerId = innerJson.getLong("key");
                if (customerId == null || customerId == 0) {
                    continue;
                }
                Integer count = innerJson.getInteger("doc_count");
                int countInt = count == null ? 0 : count;
                //逐桶处理时，firstCids塞入时进行判断 -- 在当前桶首次成交，但是前面桶可能已经成交过，则无需塞入--从而减小firstCids量级
                boolean flag = (countInt == 0 || countInt == 1) && !firstCids.contains(customerId) && !secondCids.contains(customerId);
                if (flag) {
                    innerFirstCustomerIds.add(customerId);
                    //所有当前桶标记为首次成交的客户id集合
                    firstCids.add(customerId);
                } else {
                    innerSecondCustomerIds.add(customerId);
                    secondCids.add(customerId);
//                    //循环完各桶后，移除首次成交内可能存在的复购客户（第一个桶是首次，但是在第二个桶是复购）
                    //不再移除，是因为新的方法改造后，需要在第3步基于firstCids获取startTime之前已经成交过的合同。因此需要把各个桶内所有标记为首次成交的客户都查出来校验其是不是首次成交
//                    firstCids.remove(customerId);
                }
            }
        }
        firstCustomerIds.add(innerFirstCustomerIds);
        secondCustomerIds.add(innerSecondCustomerIds);
        customerOkFirstSecondPojo.setFirstCids(firstCids);
        customerOkFirstSecondPojo.setSecondCids(secondCids);
        return customerOkFirstSecondPojo;
    }

    /**
     * 处理二层聚合的key
     * @param bucketsArray     聚合（客户id分组聚合）结果array
     * @param floorAggsKeyPojo 二层聚合key的pojo
     * @param mapKey           桶的key
     * @param resultMap        结果linked map
     * @param totalSet         排重
     * @param totalFlag        true 返回总数， false 不返回
     * @return com.xbongbong.pro.statistic.pojo.FloorAggsKeyPojo
     * @author zcp
     * @date 2019/2/23 15:43
     * @since v1.0
     * @version v1.0
     */
    private FloorAggsKeyPojo formatFloorAggsKeyPojo(JSONArray bucketsArray, FloorAggsKeyPojo floorAggsKeyPojo, String mapKey, Map<String, Set> resultMap, Set totalSet, boolean totalFlag) {
        Set set = new HashSet<>();
        for (Object object : bucketsArray) {
            JSONObject innerJson = (JSONObject) object;
            Object key = innerJson.get("key");
            //将空的key排除
            if (StringUtil.isEmpty(key.toString())) {
                continue;
            }
            set.add(key);
            if (totalFlag) {
                totalSet.add(key);
            }
        }
        resultMap.put(mapKey, set);
        return floorAggsKeyPojo;
    }

    /**
     * 为饼图的表格格式化数据
     * @param throughDto      穿透参数dto，当该值为null时，不需要穿透
     * @param chartResultPojo 统计参数pojo
     * @param dataSeries      置入图的dataSeries
     * @param legend          图legend
     * @param realNameList    表格标题要用数据字典的真实名称
     * @return java.util.List<com.xbongbong.pro.statistic.result.pojo.vo.ResultDataTableVO>
     * @author zcp
     * @date 2019/4/22 16:04
     * @since v1.0
     * @version v1.0
     */
    private List<ResultDataTableVO> formatData4PieTable(ThroughDTO throughDto, ChartResultPojo chartResultPojo, List<ResultDataSeriesChartVO> dataSeries, List<String> legend, List<String> realNameList) {
        List<ResultDataTableVO> resultDataTableVOs = new ArrayList<>();
        if (throughDto == null) {
            //不需要穿透
            for (ResultDataSeriesChartVO resultDataSeriesChartVO : dataSeries) {
                //渲染不是地图才会同步画表数据
                legend.add(resultDataSeriesChartVO.getName());
                realNameList.add(resultDataSeriesChartVO.getRealName());
                //表数据，是一个值一个对象
                ResultDataTableVO resultDataTableVO = new ResultDataTableVO(resultDataSeriesChartVO.getValue(), null);
                //表数据塞入resultDataTableVO
                resultDataTableVOs.add(resultDataTableVO);
            }
        } else {
            //处理穿透
            Integer startTime = chartResultPojo.getStartTime();
            Integer endTime = chartResultPojo.getEndTime();
            FiscalYearTimePojo fiscalYearTimePojo = chartResultPojo.getFiscalYearTimePojo();
            TimePojo timePojo = throughHelp.formatThroughTimeInfo(false, fiscalYearTimePojo, startTime, endTime);
            String unknown = I18nMessageUtil.getMessage(I18nStringConstant.UNKNOW);
            for (ResultDataSeriesChartVO resultDataSeriesChartVO: dataSeries){
                legend.add(resultDataSeriesChartVO.getName());
                String realName = resultDataSeriesChartVO.getRealName();
                String value = resultDataSeriesChartVO.getValue();
                //渲染不是地图才会同步画表数据
                realNameList.add(realName);
                //表数据，是一个值一个对象
                if (unknown.equals(realName)) {
                    ResultDataTableVO resultDataTableVO = new ResultDataTableVO(value, null);
                    //表数据塞入resultDataTableVO
                    resultDataTableVOs.add(resultDataTableVO);
                } else {
                    //设置数据字典穿透code
                    throughDto.setDictionaryCodeThrough(resultDataSeriesChartVO.getCode());
                    ResultDataTableVO resultDataTableVO = throughHelp.formatResultDataTableVO(value, throughDto, timePojo);
                    //表数据塞入resultDataTableVO
                    resultDataTableVOs.add(resultDataTableVO);
                }
            }
        }
        return resultDataTableVOs;
    }

    /**
     * 获取只有一层terms聚合的结果buckets数组
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param fieldGroup       terms聚合字段
     * @return com.alibaba.fastjson.JSONArray 聚合结果数组
     * @author zcp
     * @date 2019/2/23 16:16
     * @lastTime indexTypeEnum 核对完成 已更改为indexModel中方法 @date 2022/5/6 15:35
     * @since v1.0
     * @version v1.0
     */
    private JSONArray getTermsBuckets(IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, String fieldGroup) throws XbbException {
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("aggs01").field(fieldGroup);
        //该桶要置为最大
        termsAggregationBuilder.size(Integer.MAX_VALUE);
        termsAggregationBuilder.executionHint(ElasticConstant.EXECUTION_HINT);
        JSONObject resultJsonObject = esHelper.elasticAggs4Buckets2Object(indexTypeEnum, boolQueryBuilder, termsAggregationBuilder);
        return resultJsonObject.getJSONArray("buckets");
    }

    /**
     * 获取双层聚合的buckets数组：第一层时间分桶，第二层要terms聚合
     * @param chartResultPojo  参数pojo
     * @param indexTypeEnum    索引，用于获取index和type
     * @param boolQueryBuilder 查询条件
     * @param timeType         时间筛选类型
     * @param fieldTime        时间分桶字段
     * @param fieldGroup       terms聚合字段
     * @return com.alibaba.fastjson.JSONArray 第一层的buckets数组
     * @author zcp
     * @date 2019/2/23 16:18
     * @lastTime indexTypeEnum 核对完成 已更改为indexModel中方法 @date 2022/5/6 15:35
     * @since v1.0
     * @version v1.0
     */
    private JSONArray getDateAndTermsBuckets(ChartResultPojo chartResultPojo, IndexTypeEnum indexTypeEnum, BoolQueryBuilder boolQueryBuilder, int timeType, String fieldTime, String fieldGroup) throws XbbException {
        //双层聚合：第一层按签订时间分桶，第二层要排重（如根据客户id排重等）
        ValuesSourceAggregationBuilder valuesSourceAggregationBuilder = esHelper.getDateHistogramAggregation(chartResultPojo.getStartTime(), chartResultPojo.getEndTime(), fieldTime, timeType);
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("aggs02").field(fieldGroup);
        //第二层桶要置为最大
        termsAggregationBuilder.size(Integer.MAX_VALUE);
        termsAggregationBuilder.executionHint(ElasticConstant.EXECUTION_HINT);
        valuesSourceAggregationBuilder.subAggregation(termsAggregationBuilder);
        JSONObject resultJsonObject = esHelper.elasticAggs4Buckets2Object(indexTypeEnum, boolQueryBuilder, valuesSourceAggregationBuilder);
        return resultJsonObject.getJSONArray("buckets");
    }

    /**
     * 处理客户金额排名，如：合同金额客户排名、回款金额客户排名
     * @param seriesName          柱状图上面的柱内的名称，如“合同金额”、“回款金额”
     * @param corpid              公司id
     * @param corpRankMap         客户id--排名值（客户金额）
     * @param amountCustomerMap   客户id--金额
     * @param chartResultPojo     请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultDetailVO
     * @throws XbbException
     * @author zcp
     * @date 2019/2/28 23:09
     * @since v1.0
     * @version v1.0
     */
    private ChartResultDetailVO reuslt4CustomerTop(String seriesName, String corpid, Map<Long, Integer> corpRankMap, Map<Long, Double> amountCustomerMap, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        Set<Long> customerIdSet = new HashSet<>();
        //先循环获取出客户id、各个客户总金额
        for (Entry<Long, Integer> entry : corpRankMap.entrySet()) {
            customerIdSet.add(entry.getKey());
        }
        //处理客户名称
        List<CustomerInfoVO> list = formHelp.getCustomerListByIds(corpid, customerIdSet);
        //客户id--客户名称
        Map<Long, String> nameCustomerMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        for (CustomerInfoVO detailVO : list) {
            nameCustomerMap.put(detailVO.getId(), detailVO.getName());
        }
        return result4Top(seriesName, corpRankMap, amountCustomerMap, nameCustomerMap, chartResultPojo, chartResultDetailVO);
    }

    /**
     * 废弃原因：pro无多币种
     * 转换回款、未回款金额（根据合同的币种）
     * @param currencyRateMap 币种id--币种汇率
     * @param corpid          公司id
     * @param cidAmountMap    合同id--属于该合同的回款单总金额
     * @param contractIdIn    合同ids
     * @return double
     * @throws XbbException
     * @author zcp
     * @date 2019/3/3 15:10
     * @since v1.0
     * @version v1.0
     */
    @Deprecated
    private double convertPaymentAmount(Map<Long, Double> currencyRateMap, String corpid, Map<Long, Double> cidAmountMap, Set<Long> contractIdIn) throws XbbException {
        //-------------------处理币种
        double total = 0;
        //查出合同信息（以用其币种）
        List<ContractInfoVO> contractInfoVoList = formHelp.getContractListByIds(corpid, contractIdIn);
        //合同id--币种汇率对应关系map
        Map<Long, Double> cidRateMap = getExchangeRateMapFromContract(contractInfoVoList, currencyRateMap);
        for (Entry<Long, Double> entry : cidAmountMap.entrySet()) {
            double amount = getAmount4Payment(cidRateMap, entry);
            total += amount;
        }
        //保留2位小数
        total = Arith.div(total, 1, 2);
        return total;
    }

    /**
     * 计算属于某个时间内的金额
     * @param startTime      开始时间
     * @param endTime        结束时间
     * @param timeAmountPojo 包含两个list（大小一致，顺序对应）：timeList，amountList
     * @return java.lang.Double
     * @author zcp
     * @date 2019/3/6 20:38
     * @since v1.0
     * @version v1.0
     */
    private Double getAmountByTime(Integer startTime, Integer endTime, TimeAmountPojo timeAmountPojo) {
        //时间list
        List<Integer> timeList = timeAmountPojo.getTimeList();
        //金额list
        List<Double> amountList = timeAmountPojo.getAmountList();
        Double totalAmount = 0d;
        for (int i = 0; i < timeList.size(); i++) {
            Integer time = timeList.get(i);
            Double amount = amountList.get(i);

            if (startTime <= time && time < endTime) {
                totalAmount += amount;
            }
        }
        return totalAmount;
    }

    /**
     * 废弃原因：pro无多币种
     * 获取回款的金额---根据合同币种汇率转化为人民币
     * @param cidRateMap 合同list
     * @param entry      合同id--合同金额
     * @return double 转为人民币的金额
     * @author zcp
     * @date 2019/3/20 21:51
     * @since v1.0
     * @version v1.0
     */
    @Deprecated
    private double getAmount4Payment(Map<Long, Double> cidRateMap, Entry<Long, Double> entry) {
        Long contractId = entry.getKey();
        Double value = entry.getValue();
        //汇率
        Double rate = cidRateMap.get(contractId);
        //人民币
        return BasicHelper.getAmountByExchangeRate(value, rate);
    }

    /**
     * 工单耗时的表格数据处理
     *
     * @param timeList 时间列表
     * @return java.util.List<com.xbongbong.pro.statistic.result.pojo.vo.ResultDataTableVO>
     * @author 徐俊杰
     * @date 2019/4/19 9:43
     * @since v1.0
     */
    private List<ResultDataTableVO> workOrderTableData(List<Double> timeList, Integer size, String timeTypeStr) {
        List<ResultDataTableVO> dataTableVOS = new ArrayList<>();
        ResultDataTableVO timeType = new ResultDataTableVO(timeTypeStr, null);
        dataTableVOS.add(0, timeType);
        for (int i = 0; i < size; i++) {
            Double time;
            try {
                time = timeList.get(i);
            } catch (IndexOutOfBoundsException e) {
                time = 0D;
            }
            ResultDataTableVO resultDataTableVO = new ResultDataTableVO(String.valueOf(time), null);
            dataTableVOS.add(resultDataTableVO);
        }
        return dataTableVOS;
    }

    /**
     * 统计结果得到后直接封装好内层结果，到最终返回时直接读取使用：只按照时间分桶塞入n个map，每个map内封装数量、总金额的显示信息
     * @param chartResultPojo  请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param jsonObject 统计结果
     * @param subAggs terms聚合字段
     * @param valueStr 聚合结果内的value key
     * @param throughDTO 穿透信息
     * @param timePojo 穿透用时间pojo
     * @return java.util.Map<java.lang.String,com.xbongbong.pro.statistic.result.pojo.StageFunnelInfoPojo>
     * @throws
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    private Map<String, StageFunnelInfoPojo> formatResult4TimeAndField(ChartResultPojo chartResultPojo, JSONObject jsonObject, String subAggs, String valueStr, ThroughDTO throughDTO, TimePojo timePojo) {
        JSONArray buckets = jsonObject.getJSONArray("buckets");
                /*
                  key：分组字段，如阶段code
                  value：该分组内的总数量、及sum总值(double类型)
                 */
        Map<String, StageFunnelInfoPojo> subTermsSumMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        NumFormatEnum numFormatEnum = NumFormatEnum.getByCode(chartResultPojo.getNumFormat());
        for (Object o : buckets) {
            JSONObject subObj = (JSONObject) o;
            //terms对应的字段值，如阶段code
            String key = subObj.getString(StringConstant.KEY);
            Integer count = subObj.getInteger("doc_count");
            Double value = subObj.getJSONObject(subAggs).getDoubleValue(valueStr);
            //获取穿透through（根据code）
            throughDTO.setDictionaryCodeThrough(key);
            //处理各个桶内数据
            String countTable = ChartResultUtil.formatValue2String(false, chartResultPojo.getValueTypeEnum(), false, count.toString(), numFormatEnum, false);
            ResultDataTableVO countDataTableVo = throughHelp.formatResultDataTableVO(countTable, throughDTO, timePojo);
            String valueTable = ChartResultUtil.formatValue2String(false, ValueTypeEnum.VALUE_MONEY, false, value.toString(), numFormatEnum, false);
            ResultDataTableVO valueDataTableVo = throughHelp.formatResultDataTableVO(valueTable, throughDTO, timePojo);
            StageFunnelInfoPojo stageFunnelInfoPojo = new StageFunnelInfoPojo(countDataTableVo, valueDataTableVo);
            subTermsSumMap.put(key, stageFunnelInfoPojo);
        }
        return subTermsSumMap;
    }

    /**
     * 封装数字字段靠右展示:0，靠左 1，靠右 2，居中
     * @param titleList
     * @return
     */
    public List<TitleRightPojo> titleNumRight(List<String> titleList){
        return titleNumRight(titleList, null, null, null);
    }
    /**
     * 封装数字字段靠右展示:0，靠左 1，靠右 2，居中
     * @param titleList
     * @param children
     * @param col 合并几列
     * @param row 合并几行
     * @return
     * @update by zcp 增加入参children、col、row
     */
    public List<TitleRightPojo> titleNumRight(List<String> titleList, List<TitleRightPojo> children, Integer col, Integer row){
        List<TitleRightPojo> titleRightList = new ArrayList<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        titleList.forEach(item -> {
            TitleRightPojo titleRightPojo = new TitleRightPojo();
            titleRightPojo.setTitleValue(item);
            titleRightPojo.setNumRight(BasicConstant.ONE);
            if(!Objects.equals(isNumTitle(item), BasicConstant.ZERO)){
                titleRightPojo.setNumRight(BasicConstant.ZERO);
            }
            titleRightPojo.setCol(col);
            titleRightPojo.setRow(row);
            if (Objects.nonNull(children)) {
                titleRightPojo.setChildren(children);
            }
            titleRightList.add(titleRightPojo);
        });
        return titleRightList;
    }

    /**
     * 特殊标题排除(0，靠左 1，靠右 2，居中)
     * @param title
     * @return
     */
    private Integer isNumTitle(String title){
        Integer numRight = BasicConstant.ZERO;
        // title总结
        List<String> textTitle = Arrays.asList("产品编号", "产品名称", "产品规格", "产品分类", "产品单位", "供应商名称", "主负责人", "单据编号",
                "关联单据编号", "仓库名称", "业务类型", "业务日期", "日期", "客户行业", "客户来源", "客户状态", "客户阶段", "跟进方式", "销售机会阶段",
                "输单原因", "退货原因", "星级", "阶段", "分类",  "规格", "单位", "员工", "部门", "资金账户", "类型", "单据", "关联客户/供应商",
                "流失原因","流失前阶段","序号");
        if(textTitle.contains(title)){
            numRight = BasicConstant.ONE;
        }
        return numRight;
    }

    /**
     * 标题数据展开
     * @param titleNumRight
     * @return
     */
    public List<String> getTitleValue(List<TitleRightPojo> titleNumRight){
        List<String> titleValueList = new ArrayList<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        titleNumRight.forEach(item -> titleValueList.add(item.getTitleValue()));
        return titleValueList;
    }

    public String getUnit(String unit){
        Locale locale = LocaleContextHolder.getLocale();
        if (Objects.equals(locale.getLanguage(), I18nStringConstant.ENGLISH_LANGUAGE)) {
            return "";
        }
        return unit;
    }

    /**
     * 处理首次成交（结合agoToStartCustomerIdIn，把firstIdSet内属于复购客户的移出放入secondIdSet内）
     * @param agoToStartCustomerIdIn 时间段内
     * @param firstIdSet 时间范围内的首次成交客户id
     * @param secondIdSet 时间范围内的复购客户id
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    private void dealWithFirstSecond(Set<Long> agoToStartCustomerIdIn, Set<Long> firstIdSet, Set<Long> secondIdSet) {
        Set<Long> firstIdSetTemp = new HashSet<>(firstIdSet);
        //取交集，得到该桶内属于复购的首次成交客户（在该桶是首次，但是与时间之前的数据比较是复购）
        firstIdSetTemp.retainAll(agoToStartCustomerIdIn);
        //属于复购的塞入复购内
        secondIdSet.addAll(firstIdSetTemp);
        //移除复购的（交集，即之前已经成交过的），剩余该桶内真正的首次成交
        firstIdSet.removeAll(firstIdSetTemp);
        //把该桶所有成交（首次、再次）都塞入总成交桶
        agoToStartCustomerIdIn.addAll(firstIdSet);
        agoToStartCustomerIdIn.addAll(secondIdSet);
    }

    /**
     * 根据id获取看板配置实体
     *
     * @param corpid          公司id
     * @param userId          用户id
     * @param platform        平台
     * @param boardType       看板类型
     * @param chartCategoryId 分类id
     * @return
     * @throws
     * @author hongxiao
     * @date: 2019-03-29 14:14
     * @version v1.0
     * @since v1.0
     */
    public IndexSetDataPojo getSetDataPojo(String corpid, String userId, String platform, Integer boardType, Long chartCategoryId, Long homePageId) throws XbbException {
        // 获取个人配置
        List<IndexSetDataPojo> setDBList = null;
        if (null != homePageId){
            BoardTypeEnum boardTypeEnum = BoardTypeEnum.getByCode(boardType);
            setDBList = getCustomConfig(corpid, homePageId, boardTypeEnum.getAlias());
        }else {
            setDBList = getUserConfig(corpid, userId, boardType);
        }
        List<IndexSetDataPojo> companyDBList = null;
        if (setDBList == null) {
            // 无个人配置，直接使用公司配置
            companyDBList = getCompanyConfig(corpid, userId, platform, boardType);
            setDBList = companyDBList;
        }

        // 获取当前看板模块
        IndexSetDataPojo setDataPojo = null;
        for (IndexSetDataPojo dataPojo : setDBList) {
            // 分类id相同 && 系统默认分类
            if (chartCategoryId.equals(dataPojo.getChartCategoryId()) && ChartConstant.CHART_CATEGORY_TYPE_CATEGORY == (dataPojo.getChartCategoryType())) {
                setDataPojo = dataPojo;
                break;
            }
        }
        if(setDataPojo != null) {
            return setDataPojo;
        }

        // 个人看板配置不存在时，使用公司配置查询是否存在
        if(companyDBList == null) {
            companyDBList = getCompanyConfig(corpid, userId, platform, boardType);
        }
        for (IndexSetDataPojo dataPojo : companyDBList) {
            // 分类id相同 && 系统默认分类
            if (chartCategoryId.equals(dataPojo.getChartCategoryId()) && ChartConstant.CHART_CATEGORY_TYPE_CATEGORY == (dataPojo.getChartCategoryType())) {
                setDataPojo = dataPojo;
                break;
            }
        }

        if (setDataPojo == null) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, ErrorMessageConstant.NO_CURRENT_BOARD_PERMISSION);
        }

        return setDataPojo;
    }

    /**
     * 获取公司配置
     *
     * @param corpid    公司id
     * @param userId    员工id
     * @param platform  平台
     * @param boardType 看板类型
     * @return
     * @throws
     * @author hongxiao
     * @date: 2019-02-28 22:49
     * @version v1.0
     * @since v1.0
     */
    public List<IndexSetDataPojo> getCompanyConfig(String corpid, String userId, String platform, Integer boardType) throws XbbException {
        // 获取公司配置
        List<IndexSetDataPojo> list = getUserConfig(corpid, "0", boardType);
        if (list == null) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100018, ErrorMessageConstant.FAILED_GET_BOARD_CONFIG);
        }
        return list;
    }

    /**
     * 获取看板配置数据
     *
     * @param corpid    公司id
     * @param userId    员工id
     * @param boardType 看板类型
     * @return
     * @throws
     * @author hongxiao
     * @date: 2019-02-28 10:53
     * @version v1.0
     * @since v1.0
     */
    public List<IndexSetDataPojo> getUserConfig(String corpid, String userId, Integer boardType) {
        IndexSetEntity setEntity = indexSetModel.getByBoardType(corpid, userId, boardType);
        if (setEntity != null) {
            List<IndexSetDataPojo> list;
            try {
                list = setEntity.getContentData().toJavaList(IndexSetDataPojo.class);
            } catch (JSONException e) {
                list = null;
            }
            return list;
        }
        return null;
    }

    /**
     * 获取看板配置数据
     *
     * @param corpid    公司id
     * @param homePageId    自定义首页id
     * @param boardType 看板类型
     * @return
     * @throws
     * @author hongxiao
     * @date: 2019-02-28 10:53
     * @version v1.0
     * @since v1.0
     */
    public List<IndexSetDataPojo> getCustomConfig(String corpid, Long homePageId,String boardType) throws XbbException {
        // 管理员自定义首页分配
        HomePageManageEntity homePageManageEntity = null;
        if ( null != homePageId){
            homePageManageEntity = homePageManageModel.getByKey(homePageId,corpid);
            if (null == homePageManageEntity){
                throw new XbbException(SystemErrorCodeEnum.API_ERROR_100018,  SystemErrorCodeEnum.API_ERROR_100018.getMsg());
            }
        }
        JSONObject refIds = homePageManageEntity.getRefIds();
        IndexSetEntity setEntity = null;
        if (refIds != null){
            Long indexSetId = refIds.getLong(boardType);
            if (null != indexSetId){
                setEntity = indexSetModel.getByKey(indexSetId,corpid);
            }
        }
        if (setEntity != null) {
            List<IndexSetDataPojo> list;
            try {
                list = setEntity.getContentData().toJavaList(IndexSetDataPojo.class);
            } catch (JSONException e) {
                list = null;
            }
            return list;
        }
        return null;
    }


}
